From da690d274168c9e38a9a3a932dabaf46c4a0c670 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 10 Jul 2020 09:54:09 +0300 Subject: [PATCH] Removed source code of old touchbar. --- .../SourceFiles/platform/mac/mac_touchbar.h | 40 - .../SourceFiles/platform/mac/mac_touchbar.mm | 1911 ----------------- 2 files changed, 1951 deletions(-) delete mode 100644 Telegram/SourceFiles/platform/mac/mac_touchbar.h delete mode 100644 Telegram/SourceFiles/platform/mac/mac_touchbar.mm diff --git a/Telegram/SourceFiles/platform/mac/mac_touchbar.h b/Telegram/SourceFiles/platform/mac/mac_touchbar.h deleted file mode 100644 index 979e054e7e..0000000000 --- a/Telegram/SourceFiles/platform/mac/mac_touchbar.h +++ /dev/null @@ -1,40 +0,0 @@ -/* -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 - -#ifndef OS_OSX - -#include "platform/platform_specific.h" -#include "media/audio/media_audio.h" -#include "media/player/media_player_instance.h" -#import - -namespace Platform { - -enum class TouchBarType { - None, - Main, - AudioPlayer, - AudioPlayerForce, -}; - -} // namespace Platform - -@interface TouchBar : NSTouchBar - -@property(retain) NSDictionary * _Nullable touchBarItems; - -- (id _Nonnull) init:(NSView * _Nonnull)view - session:(not_null)session; -- (void) handleTrackStateChange:(Media::Player::TrackState)state; -- (void) setTouchBar:(Platform::TouchBarType)type; -- (void) showInputFieldItem:(bool)show; - -@end - -#endif // OS_OSX diff --git a/Telegram/SourceFiles/platform/mac/mac_touchbar.mm b/Telegram/SourceFiles/platform/mac/mac_touchbar.mm deleted file mode 100644 index 7d59280d35..0000000000 --- a/Telegram/SourceFiles/platform/mac/mac_touchbar.mm +++ /dev/null @@ -1,1911 +0,0 @@ -/* -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 "platform/mac/mac_touchbar.h" - -#ifndef OS_OSX - -#include "api/api_sending.h" -#include "apiwrap.h" -#include "app.h" -#include "base/call_delayed.h" -#include "base/platform/mac/base_utilities_mac.h" -#include "base/timer.h" -#include "base/unixtime.h" -#include "boxes/confirm_box.h" -#include "chat_helpers/emoji_list_widget.h" -#include "core/application.h" -#include "core/sandbox.h" -#include "data/data_changes.h" -#include "data/data_cloud_file.h" -#include "data/data_document.h" -#include "data/data_document_media.h" -#include "data/data_file_origin.h" -#include "data/data_folder.h" -#include "data/data_peer_values.h" -#include "data/data_session.h" -#include "data/data_user.h" -#include "data/stickers/data_stickers.h" -#include "dialogs/dialogs_layout.h" -#include "history/history.h" -#include "lang/lang_keys.h" -#include "main/main_session.h" -#include "mainwidget.h" -#include "mainwindow.h" -#include "mtproto/mtproto_config.h" -#include "styles/style_basic.h" -#include "styles/style_dialogs.h" -#include "styles/style_media_player.h" -#include "styles/style_settings.h" -#include "ui/effects/animations.h" -#include "ui/emoji_config.h" -#include "ui/empty_userpic.h" -#include "ui/widgets/input_fields.h" -#include "window/themes/window_theme.h" -#include "window/window_controller.h" -#include "window/window_session_controller.h" - -#import - -NSImage *qt_mac_create_nsimage(const QPixmap &pm); - -namespace { -//https://developer.apple.com/design/human-interface-guidelines/macos/touch-bar/touch-bar-icons-and-images/ -constexpr auto kIdealIconSize = 36; -constexpr auto kMaximumIconSize = 44; -constexpr auto kCircleDiameter = 30; -constexpr auto kPinnedButtonsSpace = 30; -constexpr auto kPinnedButtonsLeftSkip = kPinnedButtonsSpace / 2; - -constexpr auto kCommandPlayPause = 0x002; -constexpr auto kCommandPlaylistPrevious = 0x003; -constexpr auto kCommandPlaylistNext = 0x004; -constexpr auto kCommandClosePlayer = 0x005; - -constexpr auto kCommandBold = 0x010; -constexpr auto kCommandItalic = 0x011; -constexpr auto kCommandUnderline = 0x012; -constexpr auto kCommandStrikeOut = 0x013; -constexpr auto kCommandMonospace = 0x014; -constexpr auto kCommandClear = 0x015; -constexpr auto kCommandLink = 0x016; - -constexpr auto kCommandScrubberStickers = 0x020; -constexpr auto kCommandScrubberEmoji = 0x021; - -constexpr auto kOnlineCircleSize = 8; -constexpr auto kOnlineCircleStrokeWidth = 1.5; -constexpr auto kUnreadBadgeSize = 15; - -constexpr auto kMs = 1000; - -constexpr auto kSongType = AudioMsgId::Type::Song; - -constexpr auto kMaxStickerSets = 5; - -constexpr auto kGestureStateProcessed = { - NSGestureRecognizerStateChanged, - NSGestureRecognizerStateBegan, -}; - -constexpr auto kGestureStateFinished = { - NSGestureRecognizerStateEnded, - NSGestureRecognizerStateCancelled, - NSGestureRecognizerStateFailed, -}; - -NSString *const kTypePinnedPanel = @"pinnedPanel"; -NSString *const kTypeSlider = @"slider"; -NSString *const kTypeButton = @"button"; -NSString *const kTypeText = @"text"; -NSString *const kTypeTextButton = @"textButton"; -NSString *const kTypeScrubber = @"scrubber"; -NSString *const kTypePicker = @"picker"; -NSString *const kTypeFormatter = @"formatter"; -NSString *const kTypeFormatterSegment = @"formatterSegment"; - -const NSString *kCustomizationIdPlayer = @"telegram.touchbar"; -const NSString *kCustomizationIdMain = @"telegram.touchbarMain"; -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]; - -const NSTouchBarItemIdentifier kPopoverInputItemIdentifier = [NSString stringWithFormat:@"%@.popoverInput", kCustomizationIdMain]; -const NSTouchBarItemIdentifier kPopoverInputFormatterItemIdentifier = [NSString stringWithFormat:@"%@.popoverInputFormatter", kCustomizationIdMain]; - -const NSTouchBarItemIdentifier kPickerPopoverItemIdentifier = [NSString stringWithFormat:@"%@.pickerButtons", kCustomizationIdMain]; -const NSTouchBarItemIdentifier kScrubberStickersItemIdentifier = [NSString stringWithFormat:@"%@.scrubberStickers", kCustomizationIdMain]; -const NSTouchBarItemIdentifier kStickerItemIdentifier = [NSString stringWithFormat:@"%@.stickerItem", kCustomizationIdMain]; -const NSTouchBarItemIdentifier kScrubberEmojiItemIdentifier = [NSString stringWithFormat:@"%@.scrubberEmoji", kCustomizationIdMain]; -const NSTouchBarItemIdentifier kEmojiItemIdentifier = [NSString stringWithFormat:@"%@.emojiItem", kCustomizationIdMain]; -const NSTouchBarItemIdentifier kPickerTitleItemIdentifier = [NSString stringWithFormat:@"%@.pickerTitleItem", kCustomizationIdMain]; - -struct PickerScrubberItem { - PickerScrubberItem(QString title) : title(title) { - } - PickerScrubberItem(DocumentData* document) : document(document) { - } - PickerScrubberItem(EmojiPtr emoji) : emoji(emoji) { - } - QString title = QString(); - DocumentData* document = nullptr; - EmojiPtr emoji = nullptr; -}; - -enum ScrubberItemType { - Emoji, - Sticker, -}; - -using Platform::Q2NSString; - -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; -} - -NSImage *CreateNSImageFromEmoji(EmojiPtr emoji) { - const auto s = kIdealIconSize * cIntRetinaFactor(); - auto pixmap = QPixmap(s, s); - pixmap.setDevicePixelRatio(cRetinaFactor()); - pixmap.fill(Qt::black); - Painter paint(&pixmap); - PainterHighQualityEnabler hq(paint); -#ifndef OS_MAC_OLD - Ui::Emoji::Draw( - paint, - std::move(emoji), - Ui::Emoji::GetSizeTouchbar(), - 0, - 0); -#endif // OS_MAC_OLD - return [qt_mac_create_nsimage(pixmap) autorelease]; -} - -QImage PrepareImage() { - const auto s = kCircleDiameter * cIntRetinaFactor(); - auto result = QImage(QSize(s, s), QImage::Format_ARGB32_Premultiplied); - result.fill(Qt::transparent); - return result; -} - -QImage SavedMessagesUserpic() { - auto result = PrepareImage(); - Painter paint(&result); - - const auto s = result.width(); - Ui::EmptyUserpic::PaintSavedMessages(paint, 0, 0, s, s); - return result; -} - -QImage ArchiveUserpic(not_null folder) { - auto result = PrepareImage(); - Painter paint(&result); - - auto view = std::shared_ptr(); - folder->paintUserpic(paint, view, 0, 0, result.width()); - return result; -} - -QImage UnreadBadge(not_null peer) { - const auto history = peer->owner().history(peer->id); - const auto count = history->unreadCountForBadge(); - if (!count) { - return QImage(); - } - 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 = kUnreadBadgeSize * cRetinaFactor(); - unreadSt.padding = 4 * cRetinaFactor(); - unreadSt.font = style::font( - 9.5 * cRetinaFactor(), - unreadSt.font->flags(), - unreadSt.font->family()); - - auto result = QImage( - QSize(kCircleDiameter, kUnreadBadgeSize) * cIntRetinaFactor(), - QImage::Format_ARGB32_Premultiplied); - result.fill(Qt::transparent); - Painter p(&result); - - Dialogs::Layout::paintUnreadCount( - p, - unread, - result.width(), - result.height() - unreadSt.size, - unreadSt, - nullptr, - 2); - return result; -} - -NSRect PeerRectByIndex(int index) { - return NSMakeRect( - index * (kCircleDiameter + kPinnedButtonsSpace) - + kPinnedButtonsLeftSkip, - 0, - kCircleDiameter, - kCircleDiameter); -} - -TimeId CalculateOnlineTill(not_null peer) { - if (peer->isSelf()) { - return 0; - } - if (const auto user = peer->asUser()) { - if (!user->isServiceUser() && !user->isBot()) { - const auto onlineTill = user->onlineTill; - return (onlineTill <= -5) - ? -onlineTill - : (onlineTill <= 0) - ? 0 - : onlineTill; - } - } - return 0; -}; - -int WidthFromString(NSString *s) { - return (int)ceil( - [[NSTextField labelWithString:s] frame].size.width) * 1.2; -} - -inline bool IsSticker(ScrubberItemType type) { - return type == ScrubberItemType::Sticker; -} - -inline bool CurrentSongExists() { - return Media::Player::instance()->current(kSongType).audio() != nullptr; -} - -inline bool IsSelfPeer(PeerData *peer) { - return peer && peer->isSelf(); -} - -inline auto GetActiveChat() { - if (const auto window = App::wnd()) { - if (const auto controller = window->sessionController()) { - return controller->activeChatCurrent(); - } - } - return Dialogs::Key(); -} - -inline bool CanWriteToActiveChat() { - if (const auto history = GetActiveChat().history()) { - return history->peer->canWrite(); - } - return false; -} - -inline std::optional RestrictionToSendStickers() { - if (const auto peer = GetActiveChat().peer()) { - return Data::RestrictionError( - peer, - ChatRestriction::f_send_stickers); - } - return std::nullopt; -} - -QString TitleRecentlyUsed(const Data::StickersSets &sets) { - const auto it = sets.find(Data::Stickers::CloudRecentSetId); - if (it != sets.cend()) { - return it->second->title; - } - return tr::lng_recent_stickers(tr::now); -} - -NSString *FormatTime(int time) { - 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; -} - -void SendKeyEvent(int command) { - QWidget *focused = QApplication::focusWidget(); - if (!qobject_cast(focused)) { - return; - } - auto key = 0; - auto modifier = Qt::KeyboardModifiers(0) | Qt::ControlModifier; - switch (command) { - case kCommandBold: - key = Qt::Key_B; - break; - case kCommandItalic: - key = Qt::Key_I; - break; - case kCommandMonospace: - key = Qt::Key_M; - modifier |= Qt::ShiftModifier; - break; - case kCommandClear: - key = Qt::Key_N; - modifier |= Qt::ShiftModifier; - break; - case kCommandLink: - key = Qt::Key_K; - break; - case kCommandUnderline: - key = Qt::Key_U; - break; - case kCommandStrikeOut: - key = Qt::Key_X; - modifier |= Qt::ShiftModifier; - break; - } - QApplication::postEvent(focused, new QKeyEvent(QEvent::KeyPress, key, modifier)); - QApplication::postEvent(focused, new QKeyEvent(QEvent::KeyRelease, key, modifier)); -} - -void AppendStickerSet( - const Data::StickersSets &sets, - std::vector &to, - uint64 setId) { - const auto it = sets.find(setId); - if (it == sets.cend() || it->second->stickers.isEmpty()) { - return; - } - const auto set = it->second.get(); - if (set->flags & MTPDstickerSet::Flag::f_archived) { - return; - } - if (!(set->flags & MTPDstickerSet::Flag::f_installed_date)) { - return; - } - - to.emplace_back(PickerScrubberItem(set->title.isEmpty() - ? set->shortName - : set->title)); - for (const auto sticker : set->stickers) { - to.emplace_back(PickerScrubberItem(sticker)); - } -} - -void AppendRecentStickers( - const Data::StickersSets &sets, - RecentStickerPack &recentPack, - std::vector &to) { - const auto cloudIt = sets.find(Data::Stickers::CloudRecentSetId); - const auto cloudCount = (cloudIt != sets.cend()) - ? cloudIt->second->stickers.size() - : 0; - if (cloudCount > 0) { - to.emplace_back(PickerScrubberItem(cloudIt->second->title)); - auto count = 0; - for (const auto document : cloudIt->second->stickers) { - if (document->owner().stickers().isFaved(document)) { - continue; - } - to.emplace_back(PickerScrubberItem(document)); - } - } - for (const auto recent : recentPack) { - to.emplace_back(PickerScrubberItem(recent.first)); - } -} - -void AppendFavedStickers( - const Data::StickersSets &sets, - std::vector &to) { - const auto it = sets.find(Data::Stickers::FavedSetId); - const auto count = (it != sets.cend()) - ? it->second->stickers.size() - : 0; - if (!count) { - return; - } - to.emplace_back(PickerScrubberItem( - tr::lng_mac_touchbar_favorite_stickers(tr::now))); - for (const auto document : it->second->stickers) { - to.emplace_back(PickerScrubberItem(document)); - } -} - -void AppendEmojiPacks( - const Data::StickersSets &sets, - std::vector &to) { - for (auto i = 0; i != ChatHelpers::kEmojiSectionCount; ++i) { - const auto section = static_cast(i); - const auto list = (section == Ui::Emoji::Section::Recent) - ? GetRecentEmojiSection() - : Ui::Emoji::GetSection(section); - const auto title = (section == Ui::Emoji::Section::Recent) - ? TitleRecentlyUsed(sets) - : ChatHelpers::EmojiCategoryTitle(i)(tr::now); - to.emplace_back(title); - for (const auto &emoji : list) { - to.emplace_back(PickerScrubberItem(emoji)); - } - } -} - -} // namespace - -#pragma mark - PinnedDialogsPanel - -@interface PinnedDialogsPanel : NSImageView -- (id)init:(not_null)session; -- (void)dealloc; -@end // @interface PinnedDialogsPanel - -@implementation PinnedDialogsPanel { - struct Pin { - PeerData *peer = nullptr; - std::shared_ptr userpicView = nullptr; - int index = -1; - QImage userpic; - QImage unreadBadge; - - Ui::Animations::Simple shiftAnimation; - int shift = 0; - int finalShift = 0; - int deltaShift = 0; - int x = 0; - int horizontalShift = 0; - bool onTop = false; - - Ui::Animations::Simple onlineAnimation; - TimeId onlineTill = 0; - }; - rpl::lifetime _lifetime; - Main::Session* _session; - - std::vector> _pins; - QImage _savedMessages; - QImage _archive; - base::has_weak_ptr _guard; - - bool _hasArchive; - bool _selfUnpinned; - - rpl::event_stream> _touches; - - double _r, _g, _b, _a; // The online circle color. -} - -- (void)processHorizontalReorder { - // This method is a simplified version of the VerticalLayoutReorder class - // and is adapatized for horizontal use. - enum class State : uchar { - Started, - Applied, - Cancelled, - }; - - const auto currentStart = _lifetime.make_state(0); - const auto currentPeer = _lifetime.make_state(nullptr); - const auto currentState = _lifetime.make_state(State::Cancelled); - const auto currentDesiredIndex = _lifetime.make_state(-1); - const auto waitForFinish = _lifetime.make_state(false); - const auto isDragging = _lifetime.make_state(false); - - const auto indexOf = [=](PeerData *p) { - const auto i = ranges::find(_pins, p, &Pin::peer); - Assert(i != end(_pins)); - return i - begin(_pins); - }; - - const auto setHorizontalShift = [=](const auto &pin, int shift) { - if (const auto delta = shift - pin->horizontalShift) { - pin->horizontalShift = shift; - pin->x += delta; - - // Redraw a rectangle - // from the beginning point of the pin movement to the end point. - auto rect = PeerRectByIndex(indexOf(pin->peer) + [self shift]); - const auto absDelta = std::abs(delta); - rect.origin.x = pin->x - absDelta; - rect.size.width += absDelta * 2; - [self setNeedsDisplayInRect:rect]; - } - }; - - const auto updateShift = [=](not_null peer, int indexHint) { - Expects(indexHint >= 0 && indexHint < _pins.size()); - - const auto index = (_pins[indexHint]->peer->id == peer->id) - ? indexHint - : indexOf(peer); - const auto entry = _pins[index]; - entry->shift = entry->deltaShift - + std::round(entry->shiftAnimation.value(entry->finalShift)); - if (entry->deltaShift && !entry->shiftAnimation.animating()) { - entry->finalShift += entry->deltaShift; - entry->deltaShift = 0; - } - setHorizontalShift(entry, entry->shift); - }; - - const auto moveToShift = [=](int index, int shift) { - Core::Sandbox::Instance().customEnterFromEventLoop([=] { - auto &entry = _pins[index]; - if (entry->finalShift + entry->deltaShift == shift) { - return; - } - const auto peer = entry->peer; - entry->shiftAnimation.start( - [=] { updateShift(peer, index); }, - entry->finalShift, - shift - entry->deltaShift, - st::slideWrapDuration * 1); - entry->finalShift = shift - entry->deltaShift; - }); - }; - - const auto cancelCurrentByIndex = [=](int index) { - Expects(*currentPeer != nullptr); - - if (*currentState == State::Started) { - *currentState = State::Cancelled; - } - *currentPeer = nullptr; - for (auto i = 0, count = int(_pins.size()); i != count; ++i) { - moveToShift(i, 0); - } - }; - - const auto cancelCurrent = [=] { - if (*currentPeer) { - cancelCurrentByIndex(indexOf(*currentPeer)); - } - }; - - const auto updateOrder = [=](int index, int positionX) { - const auto shift = positionX - *currentStart; - const auto current = _pins[index]; - current->shiftAnimation.stop(); - current->shift = current->finalShift = shift; - setHorizontalShift(current, shift); - - const auto count = _pins.size(); - const auto currentWidth = current->userpic.width(); - const auto currentMiddle = current->x + currentWidth / 2; - *currentDesiredIndex = index; - if (shift > 0) { - auto top = current->x - shift; - for (auto next = index + 1; next != count; ++next) { - const auto &entry = _pins[next]; - top += entry->userpic.width(); - if (currentMiddle < top) { - moveToShift(next, 0); - } else { - *currentDesiredIndex = next; - moveToShift(next, -currentWidth); - } - } - for (auto prev = index - 1; prev >= 0; --prev) { - moveToShift(prev, 0); - } - } else { - for (auto next = index + 1; next != count; ++next) { - moveToShift(next, 0); - } - for (auto prev = index - 1; prev >= 0; --prev) { - const auto &entry = _pins[prev]; - if (currentMiddle >= entry->x - entry->shift + currentWidth) { - moveToShift(prev, 0); - } else { - *currentDesiredIndex = prev; - moveToShift(prev, currentWidth); - } - } - } - }; - - const auto checkForStart = [=](int positionX) { - const auto shift = positionX - *currentStart; - const auto delta = QApplication::startDragDistance(); - *isDragging = (std::abs(shift) > delta); - if (!*isDragging) { - return; - } - - *currentState = State::Started; - *currentStart += (shift > 0) ? delta : -delta; - - const auto index = indexOf(*currentPeer); - *currentDesiredIndex = index; - - // Raise the pin. - ranges::for_each(_pins, [=](const auto &pin) { - pin->onTop = false; - }); - _pins[index]->onTop = true; - - updateOrder(index, positionX); - }; - - const auto finishCurrent = [=] { - if (!*currentPeer) { - return; - } - const auto index = indexOf(*currentPeer); - if (*currentDesiredIndex == index - || *currentState != State::Started) { - cancelCurrentByIndex(index); - return; - } - const auto result = *currentDesiredIndex; - *currentState = State::Cancelled; - *currentPeer = nullptr; - - const auto current = _pins[index]; - // Since the width of all elements is the same - // we can use a single value. - current->finalShift += (index - result) * current->userpic.width(); - - if (!(current->finalShift + current->deltaShift)) { - current->shift = 0; - setHorizontalShift(current, 0); - } - current->horizontalShift = current->finalShift; - base::reorder(_pins, index, result); - - *waitForFinish = true; - // Call on end of an animation. - base::call_delayed(st::slideWrapDuration * 1, _session, [=] { - const auto guard = gsl::finally([=] { - _session->data().notifyPinnedDialogsOrderUpdated(); - *waitForFinish = false; - }); - if (index == result) { - return; - } - const auto &order = _session->data().pinnedChatsOrder( - nullptr, - FilterId()); - const auto d = (index < result) ? 1 : -1; // Direction. - for (auto i = index; i != result; i += d) { - _session->data().chatsList()->pinned()->reorder( - order.at(i).history(), - order.at(i + d).history()); - } - _session->api().savePinnedOrder(nullptr); - }); - - moveToShift(result, 0); - }; - - const auto touchBegan = [=](int touchX) { - *isDragging = false; - cancelCurrent(); - *currentStart = touchX; - if (_pins.size() < 2) { - return; - } - const auto index = [self indexFromX:*currentStart]; - if (index < 0) { - return; - } - *currentPeer = _pins[index]->peer; - }; - - const auto touchMoved = [=](int touchX) { - if (!*currentPeer) { - return; - } else if (*currentState != State::Started) { - checkForStart(touchX); - } else { - updateOrder(indexOf(*currentPeer), touchX); - } - }; - - const auto touchEnded = [=](int touchX) { - if (*isDragging) { - finishCurrent(); - return; - } - const auto step = QApplication::startDragDistance(); - if (std::abs(*currentStart - touchX) < step) { - [self performAction:touchX]; - } - }; - - _touches.events( - ) | rpl::filter([=] { - return !(*waitForFinish); - }) | rpl::start_with_next([=](not_null event) { - const auto *touches = [(event.get()).allTouches allObjects]; - if (touches.count != 1) { - cancelCurrent(); - return; - } - const auto currentPosition = [touches[0] locationInView:self].x; - switch (touches[0].phase) { - case NSTouchPhaseBegan: - return touchBegan(currentPosition); - case NSTouchPhaseMoved: - return touchMoved(currentPosition); - case NSTouchPhaseEnded: - return touchEnded(currentPosition); - } - }, _lifetime); - - _session->data().pinnedDialogsOrderUpdated( - ) | rpl::start_with_next(cancelCurrent, _lifetime); -} - -- (id)init:(not_null)session { - self = [super init]; - _session = session; - _hasArchive = _selfUnpinned = false; - _savedMessages = SavedMessagesUserpic(); - - using UpdateFlag = Data::PeerUpdate::Flag; - - const auto downloadLifetime = _lifetime.make_state(); - const auto peerChangedLifetime = _lifetime.make_state(); - const auto lastDialogsCount = _lifetime.make_state>(0); - auto &&peers = ranges::views::all( - _pins - ) | ranges::views::transform(&Pin::peer); - - const auto updatePanelSize = [=] { - const auto size = lastDialogsCount->current(); - if (self.image) { - [self.image release]; - } - self.image = [[NSImage alloc] initWithSize:NSMakeSize( - size * (kCircleDiameter + kPinnedButtonsSpace) - + kPinnedButtonsLeftSkip - - kPinnedButtonsSpace / 2, - kCircleDiameter)]; - }; - lastDialogsCount->changes( - ) | rpl::start_with_next(updatePanelSize, _lifetime); - const auto singleUserpic = [=](const auto &pin) { - if (IsSelfPeer(pin->peer)) { - pin->userpic = _savedMessages; - return; - } - auto userpic = PrepareImage(); - Painter p(&userpic); - - pin->peer->paintUserpic(p, pin->userpicView, 0, 0, userpic.width()); - userpic.setDevicePixelRatio(cRetinaFactor()); - pin->userpic = std::move(userpic); - }; - const auto updateUserpics = [=] { - ranges::for_each(_pins, singleUserpic); - *lastDialogsCount = [self shift] + std::ssize(_pins); - [self display]; - }; - const auto updateBadge = [=](const auto &pin) { - const auto peer = pin->peer; - if (IsSelfPeer(peer)) { - return; - } - pin->unreadBadge = UnreadBadge(peer); - - const auto userpicIndex = pin->index + [self shift]; - [self setNeedsDisplayInRect:PeerRectByIndex(userpicIndex)]; - }; - const auto listenToDownloaderFinished = [=] { - _session->downloaderTaskFinished( - ) | rpl::start_with_next([=] { - const auto all = ranges::all_of(_pins, [=](const auto &pin) { - return (!pin->peer->hasUserpic()) - || (pin->userpicView && pin->userpicView->image()); - }); - if (all) { - downloadLifetime->destroy(); - } - updateUserpics(); - }, *downloadLifetime); - }; - const auto processOnline = [=](const auto &pin) { - // TODO: this should be replaced - // with the global application timer for online statuses. - const auto onlineChanges = - peerChangedLifetime->make_state>(); - const auto onlineTimer = - peerChangedLifetime->make_state([=] { - onlineChanges->fire_copy({ pin->peer }); - }); - - const auto callTimer = [=](const auto &pin) { - onlineTimer->cancel(); - if (pin->onlineTill) { - const auto time = pin->onlineTill - base::unixtime::now(); - if (time > 0) { - onlineTimer->callOnce(time * crl::time(1000)); - } - } - }; - callTimer(pin); - - using PeerUpdate = Data::PeerUpdate; - auto to_peer = rpl::map([=](const PeerUpdate &update) -> PeerData* { - return update.peer; - }); - rpl::merge( - _session->changes().peerUpdates( - pin->peer, - UpdateFlag::OnlineStatus) | to_peer, - onlineChanges->events() - ) | rpl::start_with_next([=](PeerData *peer) { - const auto it = ranges::find(_pins, peer, &Pin::peer); - if (it == end(_pins)) { - return; - } - const auto pin = *it; - pin->onlineTill = CalculateOnlineTill(pin->peer); - - callTimer(pin); - - if (![NSApplication sharedApplication].active) { - pin->onlineAnimation.stop(); - return; - } - const auto online = Data::OnlineTextActive( - pin->onlineTill, - base::unixtime::now()); - if (pin->onlineAnimation.animating()) { - pin->onlineAnimation.change( - online ? 1. : 0., - st::dialogsOnlineBadgeDuration); - } else { - const auto s = kOnlineCircleSize + kOnlineCircleStrokeWidth; - Core::Sandbox::Instance().customEnterFromEventLoop([=] { - pin->onlineAnimation.start( - [=] { - [self setNeedsDisplayInRect:NSMakeRect( - pin->x + kCircleDiameter - s, - 0, - s, - s)]; - }, - online ? 0. : 1., - online ? 1. : 0., - st::dialogsOnlineBadgeDuration); - }); - } - }, *peerChangedLifetime); - }; - - const auto updatePinnedChats = [=] { - _pins = ranges::view::zip( - _session->data().pinnedChatsOrder(nullptr, FilterId()), - ranges::view::ints(0, ranges::unreachable) - ) | ranges::views::transform([=](const auto &pair) { - const auto index = pair.second; - auto peer = pair.first.history()->peer; - auto view = peer->createUserpicView(); - const auto onlineTill = CalculateOnlineTill(peer); - Pin pin = { - .peer = std::move(peer), - .userpicView = std::move(view), - .index = index, - .onlineTill = onlineTill }; - return std::make_shared(std::move(pin)); - }); - _selfUnpinned = ranges::none_of(peers, &PeerData::isSelf); - - peerChangedLifetime->destroy(); - for (const auto &pin : _pins) { - const auto peer = pin->peer; - _session->changes().peerUpdates( - peer, - UpdateFlag::Photo - ) | rpl::start_with_next( - listenToDownloaderFinished, - *peerChangedLifetime); - - if (const auto user = peer->asUser()) { - if (!user->isServiceUser() - && !user->isBot() - && !peer->isSelf()) { - processOnline(pin); - } - } - - rpl::merge( - _session->changes().historyUpdates( - _session->data().history(peer), - Data::HistoryUpdate::Flag::UnreadView - ) | rpl::to_empty, - _session->changes().peerFlagsValue( - peer, - UpdateFlag::Notifications - ) | rpl::to_empty - ) | rpl::start_with_next([=] { - updateBadge(pin); - }, *peerChangedLifetime); - } - - updateUserpics(); - }; - - rpl::single( - rpl::empty_value() - ) | rpl::then( - _session->data().pinnedDialogsOrderUpdated() - ) | rpl::start_with_next(updatePinnedChats, _lifetime); - - const auto ArchiveId = Data::Folder::kId; - rpl::single( - rpl::empty_value() - ) | rpl::map([=] { - return _session->data().folderLoaded(ArchiveId); - }) | rpl::then( - _session->data().chatsListChanges() - ) | rpl::filter([](Data::Folder *folder) { - return folder && (folder->id() == ArchiveId); - }) | rpl::start_with_next([=](Data::Folder *folder) { - _hasArchive = !folder->chatsList()->empty(); - if (_archive.isNull()) { - _archive = ArchiveUserpic(folder); - } - updateUserpics(); - }, _lifetime); - - const auto updateOnlineColor = [=] { - st::dialogsOnlineBadgeFg->c.getRgbF(&_r, &_g, &_b, &_a); - }; - updateOnlineColor(); - - base::ObservableViewer( - *Window::Theme::Background() - ) | rpl::filter([](const Window::Theme::BackgroundUpdate &update) { - return update.paletteChanged(); - }) | rpl::start_with_next([=] { - crl::on_main(&_guard, [=] { - updateOnlineColor(); - if (const auto f = _session->data().folderLoaded(ArchiveId)) { - _archive = ArchiveUserpic(f); - } - _savedMessages = SavedMessagesUserpic(); - updateUserpics(); - }); - }, _lifetime); - - listenToDownloaderFinished(); - [self processHorizontalReorder]; - return self; -} - -- (void)dealloc { - if (self.image) { - [self.image release]; - } - [super dealloc]; -} - -- (int)shift { - return (_hasArchive ? 1 : 0) + (_selfUnpinned ? 1 : 0); -} - -- (void)touchesBeganWithEvent:(NSEvent *)event { - _touches.fire(std::move(event)); -} - -- (void)touchesMovedWithEvent:(NSEvent *)event { - _touches.fire(std::move(event)); -} - -- (void)touchesEndedWithEvent:(NSEvent *)event { - _touches.fire(std::move(event)); -} - -- (int)indexFromX:(int)position { - const auto x = position - - kPinnedButtonsLeftSkip - + kPinnedButtonsSpace / 2; - return x / (kCircleDiameter + kPinnedButtonsSpace) - [self shift]; -} - -- (void)performAction:(int)xPosition { - const auto index = [self indexFromX:xPosition]; - const auto peer = (index < 0 || index >= std::ssize(_pins)) - ? nullptr - : _pins[index]->peer; - if (!peer && !_hasArchive && !_selfUnpinned) { - return; - } - - const auto active = Core::App().activeWindow(); - const auto controller = active ? active->sessionController() : nullptr; - const auto openFolder = [=] { - const auto folder = _session->data().folderLoaded(Data::Folder::kId); - if (folder && controller) { - controller->openFolder(folder); - } - }; - Core::Sandbox::Instance().customEnterFromEventLoop([=] { - (_hasArchive && (index == (_selfUnpinned ? -2 : -1))) - ? openFolder() - : controller->content()->choosePeer( - (_selfUnpinned && index == -1) - ? _session->userPeerId() - : peer->id, - ShowAtUnreadMsgId); - }); -} - -- (QImage)imageToDraw:(int)i { - Expects(i < std::ssize(_pins)); - if (i < 0) { - if (_hasArchive && (i == -[self shift])) { - return _archive; - } else if (_selfUnpinned) { - return _savedMessages; - } - } - return _pins[i]->userpic; -} - -- (void)drawSinglePin:(int)i rect:(NSRect)dirtyRect { - const auto rect = [&] { - auto rect = PeerRectByIndex(i + [self shift]); - if (i < 0) { - return rect; - } - auto &pin = _pins[i]; - // We can have x = 0 when the pin is dragged. - rect.origin.x = ((!pin->x && !pin->onTop) ? rect.origin.x : pin->x); - pin->x = rect.origin.x; - return rect; - }(); - if (!NSIntersectsRect(rect, dirtyRect)) { - return; - } - CGContextRef context = [[NSGraphicsContext currentContext] CGContext]; - { - CGImageRef image = ([self imageToDraw:i]).toCGImage(); - CGContextDrawImage(context, rect, image); - CGImageRelease(image); - } - - if (i >= 0) { - const auto &pin = _pins[i]; - const auto rectRight = NSMaxX(rect); - if (!pin->unreadBadge.isNull()) { - CGImageRef image = pin->unreadBadge.toCGImage(); - const auto w = CGImageGetWidth(image) / cRetinaFactor(); - const auto borderRect = CGRectMake( - rectRight - w, - 0, - w, - CGImageGetHeight(image) / cRetinaFactor()); - CGContextDrawImage(context, borderRect, image); - CGImageRelease(image); - return; - } - const auto online = Data::OnlineTextActive( - pin->onlineTill, - base::unixtime::now()); - const auto value = pin->onlineAnimation.value(online ? 1. : 0.); - if (value < 0.05) { - return; - } - const auto lineWidth = kOnlineCircleStrokeWidth; - const auto circleSize = kOnlineCircleSize; - const auto progress = value * circleSize; - const auto diff = (circleSize - progress) / 2; - const auto borderRect = CGRectMake( - rectRight - circleSize + diff - lineWidth / 2, - diff, - progress, - progress); - - CGContextSetRGBStrokeColor(context, 0, 0, 0, 1.0); - CGContextSetRGBFillColor(context, _r, _g, _b, _a); - CGContextSetLineWidth(context, lineWidth); - CGContextFillEllipseInRect(context, borderRect); - CGContextStrokeEllipseInRect(context, borderRect); - } -} - -- (void)drawRect:(NSRect)dirtyRect { - const auto shift = [self shift]; - if (_pins.empty() && !shift) { - return; - } - auto indexToTop = -1; - const auto guard = gsl::finally([&] { - if (indexToTop >= 0) { - [self drawSinglePin:indexToTop rect:dirtyRect]; - } - }); - for (auto i = -shift; i < std::ssize(_pins); i++) { - if (i >= 0 && _pins[i]->onTop && (indexToTop < 0)) { - indexToTop = i; - continue; - } - [self drawSinglePin:i rect:dirtyRect]; - } -} - -@end // @@implementation PinnedDialogsPanel - -#pragma mark - End PinnedDialogsPanel - - -@interface PickerScrubberItemView : NSScrubberItemView -@end // @interface PickerScrubberItemView -@implementation PickerScrubberItemView { - rpl::lifetime _lifetime; - std::shared_ptr _media; - Image *_image; - QImage _qimage; - @public - DocumentData *documentData; -} - -- (instancetype)initWithFrame:(NSRect)frameRect { - self = [super initWithFrame:frameRect]; - return self; -} - -- (void)drawRect:(NSRect)dirtyRect { - if (_qimage.isNull()) { - [[NSColor blackColor] setFill]; - NSRectFill(dirtyRect); - } else { - CGContextRef context = [[NSGraphicsContext currentContext] CGContext]; - CGImageRef image = _qimage.toCGImage(); - CGContextDrawImage(context, dirtyRect, image); - CGImageRelease(image); - } -} - -- (void)addDocument:(not_null)document { - if (!document->sticker()) { - return; - } - documentData = document; - _media = document->createMediaView(); - _media->checkStickerSmall(); - _image = _media->getStickerSmall(); - if (_image) { - [self updateImage]; - return; - } - document->session().downloaderTaskFinished( - ) | rpl::start_with_next([=] { - _image = _media->getStickerSmall(); - if (_image) { - [self updateImage]; - _lifetime.destroy(); - } - }, _lifetime); -} - -- (void)updateImage { - const auto size = _image->size() - .scaled(kCircleDiameter, kCircleDiameter, Qt::KeepAspectRatio); - _qimage = _image->pixSingle( - size.width(), - size.height(), - kCircleDiameter, - kCircleDiameter, - ImageRoundRadius::None).toImage(); - [self display]; -} -@end // @implementation PickerScrubberItemView - - -@interface PickerCustomTouchBarItem: NSCustomTouchBarItem - -@end // @interface PickerCustomTouchBarItem - -#pragma mark - - -@implementation PickerCustomTouchBarItem { - std::vector _stickers; - NSPopoverTouchBarItem *_parentPopover; - DocumentId _lastPreviewedSticker; - Main::Session* _session; -} - -- (id) init:(ScrubberItemType)type - popover:(NSPopoverTouchBarItem *)popover - session:(not_null)session { - self = [super initWithIdentifier:IsSticker(type) - ? kScrubberStickersItemIdentifier - : kScrubberEmojiItemIdentifier]; - if (!self) { - return self; - } - _session = session; - _parentPopover = popover; - IsSticker(type) ? [self updateStickers] : [self updateEmoji]; - NSScrubber *scrubber = [[[NSScrubber alloc] initWithFrame:NSZeroRect] autorelease]; - NSScrubberFlowLayout *layout = [[[NSScrubberFlowLayout alloc] init] autorelease]; - layout.itemSpacing = 10; - scrubber.scrubberLayout = layout; - scrubber.mode = NSScrubberModeFree; - scrubber.delegate = self; - scrubber.dataSource = self; - scrubber.floatsSelectionViews = true; - scrubber.showsAdditionalContentIndicators = true; - scrubber.itemAlignment = NSScrubberAlignmentCenter; - [scrubber registerClass:[PickerScrubberItemView class] forItemIdentifier:kStickerItemIdentifier]; - [scrubber registerClass:[NSScrubberTextItemView class] forItemIdentifier:kPickerTitleItemIdentifier]; - [scrubber registerClass:[NSScrubberImageItemView class] forItemIdentifier:kEmojiItemIdentifier]; - - if (IsSticker(type)) { - auto *gesture = [[NSPressGestureRecognizer alloc] - initWithTarget:self - action:@selector(gesturePreviewHandler:)]; - gesture.allowedTouchTypes = NSTouchTypeMaskDirect; - gesture.minimumPressDuration = QApplication::startDragTime() / 1000.; - gesture.allowableMovement = 0; - [scrubber addGestureRecognizer:gesture]; - } - _lastPreviewedSticker = 0; - - self.view = scrubber; - return self; -} - -- (void)gesturePreviewHandler:(NSPressGestureRecognizer *)gesture { - const auto customEnter = [](const auto callback) { - Core::Sandbox::Instance().customEnterFromEventLoop([=] { - if (App::wnd()) { - callback(); - } - }); - }; - - const auto checkState = [&](const auto &states) { - return ranges::contains(states, gesture.state); - }; - - if (checkState(kGestureStateProcessed)) { - NSScrollView *scrollView = self.view; - auto *container = scrollView.documentView.subviews.firstObject; - if (!container) { - return; - } - const auto point = [gesture locationInView:(std::move(container))]; - - for (PickerScrubberItemView *item in container.subviews) { - const auto &doc = item->documentData; - if (![item isMemberOfClass:[PickerScrubberItemView class]] - || !doc - || (doc->id == _lastPreviewedSticker) - || !NSPointInRect(point, item.frame)) { - continue; - } - _lastPreviewedSticker = doc->id; - customEnter([doc = std::move(doc)] { - App::wnd()->showMediaPreview(Data::FileOrigin(), doc); - }); - break; - } - } else if (checkState(kGestureStateFinished)) { - customEnter([] { App::wnd()->hideMediaPreview(); }); - _lastPreviewedSticker = 0; - } -} - -- (void)encodeWithCoder:(nonnull NSCoder *)aCoder { - // Has not been implemented. -} - -#pragma mark - NSScrubberDelegate - -- (NSInteger)numberOfItemsForScrubber:(NSScrubber *)scrubber { - return _stickers.size(); -} - -- (NSScrubberItemView *)scrubber:(NSScrubber *)scrubber viewForItemAtIndex:(NSInteger)index { - const auto item = _stickers[index]; - if (const auto document = item.document) { - PickerScrubberItemView *itemView = [scrubber makeItemWithIdentifier:kStickerItemIdentifier owner:nil]; - [itemView addDocument:(std::move(document))]; - return itemView; - } else if (const auto emoji = item.emoji) { - NSScrubberImageItemView *itemView = [scrubber makeItemWithIdentifier:kEmojiItemIdentifier owner:nil]; - itemView.imageView.image = CreateNSImageFromEmoji(emoji); - return itemView; - } else { - NSScrubberTextItemView *itemView = [scrubber makeItemWithIdentifier:kPickerTitleItemIdentifier owner:nil]; - itemView.textField.stringValue = Q2NSString(item.title); - return itemView; - } -} - -- (NSSize)scrubber:(NSScrubber *)scrubber layout:(NSScrubberFlowLayout *)layout sizeForItemAtIndex:(NSInteger)index { - if (const auto t = _stickers[index].title; !t.isEmpty()) { - return NSMakeSize( - WidthFromString(Q2NSString(t)) + kCircleDiameter, kCircleDiameter); - } - return NSMakeSize(kCircleDiameter, kCircleDiameter); -} - -- (void)scrubber:(NSScrubber *)scrubber didSelectItemAtIndex:(NSInteger)index { - if (!CanWriteToActiveChat()) { - return; - } - scrubber.selectedIndex = -1; - const auto chat = GetActiveChat(); - - const auto callback = [&]() -> bool { - if (const auto document = _stickers[index].document) { - if (const auto error = RestrictionToSendStickers()) { - Ui::show(Box(*error)); - } - Api::SendExistingDocument( - Api::MessageToSend(chat.history()), - document); - return true; - } else if (const auto emoji = _stickers[index].emoji) { - if (const auto inputField = qobject_cast( - QApplication::focusWidget())) { - Ui::InsertEmojiAtCursor(inputField->textCursor(), emoji); - AddRecentEmoji(emoji); - return true; - } - } - return false; - }; - - if (!Core::Sandbox::Instance().customEnterFromEventLoop(callback)) { - return; - } - - if (_parentPopover) { - [_parentPopover dismissPopover:nil]; - } -} - -- (void)updateStickers { - auto &stickers = _session->data().stickers(); - std::vector temp; - if (const auto error = RestrictionToSendStickers()) { - temp.emplace_back(PickerScrubberItem( - tr::lng_restricted_send_stickers_all(tr::now))); - _stickers = std::move(temp); - return; - } - AppendFavedStickers(stickers.sets(), temp); - AppendRecentStickers(stickers.sets(), stickers.getRecentPack(), temp); - auto count = 0; - for (const auto setId : stickers.setsOrderRef()) { - AppendStickerSet(stickers.sets(), temp, setId); - if (++count == kMaxStickerSets) { - break; - } - } - if (!temp.size()) { - temp.emplace_back(PickerScrubberItem( - tr::lng_stickers_nothing_found(tr::now))); - } - _stickers = std::move(temp); -} - -- (void)updateEmoji { - std::vector temp; - AppendEmojiPacks(_session->data().stickers().sets(), temp); - _stickers = std::move(temp); -} - -@end // @implementation PickerCustomTouchBarItem - - -@interface TouchBar() -@end // @interface TouchBar - -@implementation TouchBar { - NSView *_parentView; - - NSTouchBar *_touchBarMain; - NSTouchBar *_touchBarAudioPlayer; - - NSPopoverTouchBarItem *_popoverPicker; - - Platform::TouchBarType _touchBarType; - Platform::TouchBarType _touchBarTypeBeforeLock; - - Main::Session* _session; - - double _duration; - double _position; - - rpl::lifetime _lifetime; - rpl::lifetime _lifetimeSessionControllerChecker; -} - -- (id) init:(NSView *)view session:(not_null)session { - self = [super init]; - if (!self) { - return nil; - } - _session = session; - - const auto iconSize = kIdealIconSize / 3; - _position = 0; - _duration = 0; - _parentView = view; - self.touchBarItems = @{ - kPinnedPanelItemIdentifier: [NSMutableDictionary dictionaryWithDictionary:@{ - @"type": kTypePinnedPanel, - }], - kSeekBarItemIdentifier: [NSMutableDictionary dictionaryWithDictionary:@{ - @"type": kTypeSlider, - @"name": @"Seek Bar" - }], - kPlayItemIdentifier: [NSMutableDictionary dictionaryWithDictionary:@{ - @"type": kTypeButton, - @"name": @"Play Button", - @"cmd": [NSNumber numberWithInt:kCommandPlayPause], - @"image": CreateNSImageFromStyleIcon(st::touchBarIconPlayerPause, iconSize), - @"imageAlt": CreateNSImageFromStyleIcon(st::touchBarIconPlayerPlay, iconSize), - }], - kPreviousItemIdentifier: [NSMutableDictionary dictionaryWithDictionary:@{ - @"type": kTypeButton, - @"name": @"Previous Playlist Item", - @"cmd": [NSNumber numberWithInt:kCommandPlaylistPrevious], - @"image": CreateNSImageFromStyleIcon(st::touchBarIconPlayerPrevious, iconSize), - }], - kNextItemIdentifier: [NSMutableDictionary dictionaryWithDictionary:@{ - @"type": kTypeButton, - @"name": @"Next Playlist Item", - @"cmd": [NSNumber numberWithInt:kCommandPlaylistNext], - @"image": CreateNSImageFromStyleIcon(st::touchBarIconPlayerNext, iconSize), - }], - kCommandClosePlayerItemIdentifier: [NSMutableDictionary dictionaryWithDictionary:@{ - @"type": kTypeButton, - @"name": @"Close Player", - @"cmd": [NSNumber numberWithInt:kCommandClosePlayer], - @"image": CreateNSImageFromStyleIcon(st::touchBarIconPlayerClose, iconSize), - }], - kCurrentPositionItemIdentifier: [NSMutableDictionary dictionaryWithDictionary:@{ - @"type": kTypeText, - @"name": @"Current Position" - }], - kPopoverInputItemIdentifier: [NSMutableDictionary dictionaryWithDictionary:@{ - @"type": kTypeFormatter, - @"image": [NSImage imageNamed:NSImageNameTouchBarTextItalicTemplate], - }], - kPopoverInputFormatterItemIdentifier: [NSMutableDictionary dictionaryWithDictionary:@{ - @"type": kTypeFormatterSegment, - }], - kScrubberStickersItemIdentifier: [NSMutableDictionary dictionaryWithDictionary:@{ - @"type": kTypeScrubber, - @"cmd": [NSNumber numberWithInt:kCommandScrubberStickers], - }], - kScrubberEmojiItemIdentifier: [NSMutableDictionary dictionaryWithDictionary:@{ - @"type": kTypeScrubber, - @"cmd": [NSNumber numberWithInt:kCommandScrubberEmoji], - }], - kPickerPopoverItemIdentifier: [NSMutableDictionary dictionaryWithDictionary:@{ - @"type": kTypePicker, - @"name": @"Picker", - }] - }; - - [self createTouchBar]; - [self setTouchBar:Platform::TouchBarType::Main]; - _touchBarTypeBeforeLock = Platform::TouchBarType::Main; - - Media::Player::instance()->playerWidgetToggled( - ) | rpl::start_with_next([=](bool toggled) { - if (!toggled) { - [self setTouchBar:Platform::TouchBarType::Main]; - } else { - [self setTouchBar:Platform::TouchBarType::AudioPlayer]; - } - }, _lifetime); - - Media::Player::instance()->updatedNotifier( - ) | rpl::start_with_next([=](const Media::Player::TrackState &state) { - [self handleTrackStateChange:state]; - }, _lifetime); - - Core::App().passcodeLockChanges( - ) | rpl::start_with_next([=](bool locked) { - if (locked) { - _touchBarTypeBeforeLock = _touchBarType; - [self setTouchBar:Platform::TouchBarType::None]; - } else { - [self setTouchBar:_touchBarTypeBeforeLock]; - } - }, _lifetime); - - // At the time of this touchbar creation the sessionController does - // not yet exist. But at the time of chatsListChanges event - // the sessionController is valid and we can work with it. - // So _lifetimeSessionControllerChecker is needed only once. - _session->data().chatsListChanges( - ) | rpl::start_with_next([=] { - if (const auto window = App::wnd()) { - if (const auto controller = window->sessionController()) { - if (_session->data().stickers().setsRef().empty()) { - _session->api().updateStickers(); - } - _lifetimeSessionControllerChecker.destroy(); - controller->activeChatChanges( - ) | rpl::start_with_next([=](Dialogs::Key key) { - const auto show = key.peer() - && key.history() - && key.peer()->canWrite(); - [self showPickerItem:show]; - }, _lifetime); - } - } - }, _lifetimeSessionControllerChecker); - - rpl::merge( - _session->data().stickers().updated(), - _session->data().stickers().recentUpdated() - ) | rpl::start_with_next([=] { - [self updatePickerPopover:ScrubberItemType::Sticker]; - }, _lifetime); - - rpl::merge( - UpdatedRecentEmoji(), - Ui::Emoji::Updated() - ) | rpl::start_with_next([=] { - [self updatePickerPopover:ScrubberItemType::Emoji]; - }, _lifetime); - - return self; -} - -- (nullable NSTouchBarItem *) touchBar:(NSTouchBar *)touchBar - makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { - const id dictionaryItem = self.touchBarItems[identifier]; - const id type = dictionaryItem[@"type"]; - const auto isType = [type](NSString *string) { - return [type isEqualToString:string]; - }; - if (isType(kTypeSlider)) { - NSSliderTouchBarItem *item = [[NSSliderTouchBarItem alloc] initWithIdentifier:identifier]; - item.slider.minValue = 0.0f; - item.slider.maxValue = 1.0f; - item.target = self; - item.action = @selector(seekbarChanged:); - item.customizationLabel = dictionaryItem[@"name"]; - [dictionaryItem setObject:item.slider forKey:@"view"]; - return item; - } else if (isType(kTypeButton) || isType(kTypeTextButton)) { - NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; - NSButton *button = isType(kTypeButton) - ? [NSButton buttonWithImage:dictionaryItem[@"image"] - target:self action:@selector(buttonAction:)] - : [NSButton buttonWithTitle:dictionaryItem[@"name"] - target:self action:@selector(buttonAction:)]; - button.tag = [dictionaryItem[@"cmd"] intValue]; - item.view = button; - item.customizationLabel = dictionaryItem[@"name"]; - [dictionaryItem setObject:button forKey:@"view"]; - return item; - } else if (isType(kTypeText)) { - NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; - NSTextField *text = [NSTextField labelWithString:@"00:00 / 00:00"]; - text.alignment = NSTextAlignmentCenter; - item.view = text; - item.customizationLabel = dictionaryItem[@"name"]; - [dictionaryItem setObject:text forKey:@"view"]; - return item; - } else if (isType(kTypeFormatter)) { - NSPopoverTouchBarItem *item = [[NSPopoverTouchBarItem alloc] initWithIdentifier:identifier]; - item.collapsedRepresentationImage = dictionaryItem[@"image"]; - NSTouchBar *secondaryTouchBar = [[NSTouchBar alloc] init]; - secondaryTouchBar.delegate = self; - secondaryTouchBar.defaultItemIdentifiers = @[kPopoverInputFormatterItemIdentifier]; - item.popoverTouchBar = secondaryTouchBar; - return item; - } else if (isType(kTypeFormatterSegment)) { - NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; - NSScrollView *scroll = [[NSScrollView alloc] init]; - NSSegmentedControl *segment = [[NSSegmentedControl alloc] init]; - segment.segmentStyle = NSSegmentStyleRounded; - segment.target = self; - segment.action = @selector(formatterClicked:); - - static const auto strings = { - tr::lng_menu_formatting_bold, - tr::lng_menu_formatting_italic, - tr::lng_menu_formatting_underline, - tr::lng_menu_formatting_strike_out, - tr::lng_menu_formatting_monospace, - tr::lng_menu_formatting_clear, - tr::lng_info_link_label, - }; - segment.segmentCount = strings.size(); - auto width = 0; - auto count = 0; - for (const auto s : strings) { - const auto string = Q2NSString(s(tr::now)); - width += WidthFromString(string) * 1.4; - [segment setLabel:string forSegment:count++]; - } - segment.frame = NSMakeRect(0, 0, width, kCircleDiameter); - [scroll setDocumentView:segment]; - item.view = scroll; - return item; - } else if (isType(kTypeScrubber)) { - const auto isSticker = ([dictionaryItem[@"cmd"] intValue] - == kCommandScrubberStickers); - const auto type = isSticker - ? ScrubberItemType::Sticker - : ScrubberItemType::Emoji; - const auto popover = isSticker - ? _popoverPicker - : nil; - PickerCustomTouchBarItem *item = [[PickerCustomTouchBarItem alloc] - init:type popover:popover session:_session]; - return item; - } else if (isType(kTypePicker)) { - NSPopoverTouchBarItem *item = [[NSPopoverTouchBarItem alloc] initWithIdentifier:identifier]; - _popoverPicker = item; - NSSegmentedControl *segment = [[NSSegmentedControl alloc] init]; - [self updatePickerPopover:ScrubberItemType::Sticker]; - [self updatePickerPopover:ScrubberItemType::Emoji]; - const auto imageSize = kIdealIconSize / 3 * 2; - segment.segmentStyle = NSSegmentStyleSeparated; - segment.segmentCount = 2; - [segment setImage:CreateNSImageFromStyleIcon(st::settingsIconStickers, imageSize) forSegment:0]; - [segment setImage:CreateNSImageFromStyleIcon(st::settingsIconEmoji, imageSize) forSegment:1]; - [segment setWidth:92 forSegment:0]; - [segment setWidth:92 forSegment:1]; - segment.target = self; - segment.action = @selector(segmentClicked:); - segment.trackingMode = NSSegmentSwitchTrackingMomentary; - item.visibilityPriority = NSTouchBarItemPriorityHigh; - item.collapsedRepresentation = segment; - return item; - } else if (isType(kTypePinnedPanel)) { - auto *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; - item.customizationLabel = @"Pinned Panel"; - item.view = [[PinnedDialogsPanel alloc] init:_session]; - [dictionaryItem setObject:item.view forKey:@"view"]; - return item; - } - - return nil; -} - -- (void) createTouchBar { - _touchBarMain = [[NSTouchBar alloc] init]; - _touchBarMain.delegate = self; - [self showItemInMain:nil]; - - _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]; -} - -- (void) setTouchBar:(Platform::TouchBarType)type { - if (_touchBarType == type) { - return; - } - if (type == Platform::TouchBarType::Main) { - [_parentView setTouchBar:_touchBarMain]; - } else if (type == Platform::TouchBarType::AudioPlayer) { - if (!CurrentSongExists() - || Media::Player::instance()->getActiveType() != kSongType) { - return; - } - [_parentView setTouchBar:_touchBarAudioPlayer]; - } else if (type == Platform::TouchBarType::AudioPlayerForce) { - [_parentView setTouchBar:_touchBarAudioPlayer]; - _touchBarType = Platform::TouchBarType::AudioPlayer; - return; - } else if (type == Platform::TouchBarType::None) { - [_parentView setTouchBar:nil]; - } - _touchBarType = type; -} - -- (void) showInputFieldItem:(bool)show { - [self showItemInMain: show - ? kPopoverInputItemIdentifier - : CanWriteToActiveChat() - ? kPickerPopoverItemIdentifier - : nil]; -} - -- (void) showPickerItem:(bool)show { - [self showItemInMain: show - ? kPickerPopoverItemIdentifier - : nil]; -} - -- (void) showItemInMain:(NSTouchBarItemIdentifier)item { - NSMutableArray *items = [NSMutableArray arrayWithArray:@[kPinnedPanelItemIdentifier]]; - if (item) { - [items addObject:item]; - } - _touchBarMain.defaultItemIdentifiers = items; -} - -- (void) updatePickerPopover:(ScrubberItemType)type { - NSTouchBar *secondaryTouchBar = [[NSTouchBar alloc] init]; - secondaryTouchBar.delegate = self; - const auto popover = IsSticker(type) - ? _popoverPicker - : nil; - [[PickerCustomTouchBarItem alloc] init:type popover:popover session:_session]; - const auto identifier = IsSticker(type) - ? kScrubberStickersItemIdentifier - : kScrubberEmojiItemIdentifier; - secondaryTouchBar.defaultItemIdentifiers = @[identifier]; - _popoverPicker.popoverTouchBar = secondaryTouchBar; -} - -// Audio Player Touchbar. - -- (void) handleTrackStateChange:(Media::Player::TrackState)state { - if (state.id.type() == kSongType) { - if (_touchBarType != Platform::TouchBarType::None) { - [self setTouchBar:Platform::TouchBarType::AudioPlayerForce]; - } - } else { - return; - } - _position = state.position < 0 ? 0 : state.position; - _duration = state.length; - if (Media::Player::IsStoppedOrStopping(state.state)) { - _position = 0; - _duration = 0; - } - [self updateTouchBarTimeItem]; - - NSButton *playButton = self.touchBarItems[kPlayItemIdentifier][@"view"]; - const auto imgButton = (state.state == Media::Player::State::Playing) - ? @"image" - : @"imageAlt"; - playButton.image = self.touchBarItems[kPlayItemIdentifier][imgButton]; - - [self.touchBarItems[kNextItemIdentifier][@"view"] - setEnabled:Media::Player::instance()->nextAvailable(kSongType)]; - [self.touchBarItems[kPreviousItemIdentifier][@"view"] - setEnabled:Media::Player::instance()->previousAvailable(kSongType)]; -} - -- (void) updateTouchBarTimeItem { - const id item = self.touchBarItems[kCurrentPositionItemIdentifier]; - NSSlider *seekSlider = self.touchBarItems[kSeekBarItemIdentifier][@"view"]; - NSTextField *textField = item[@"view"]; - - if (_duration <= 0) { - seekSlider.enabled = NO; - seekSlider.doubleValue = 0; - } else { - seekSlider.enabled = YES; - if (!seekSlider.highlighted) { - seekSlider.doubleValue = (_position / _duration) * seekSlider.maxValue; - } - } - const auto timeToString = [&](int t) { - return FormatTime((int)floor(t / kMs)); - }; - textField.stringValue = [NSString stringWithFormat:@"%@ / %@", - timeToString(_position), - timeToString(_duration)]; - - NSTextField *field = item[@"view"]; - - if (!field) { - return; - } - - [field removeConstraint:item[@"constrain"]]; - - NSString *fString = [[textField.stringValue componentsSeparatedByCharactersInSet: - [NSCharacterSet decimalDigitCharacterSet]] componentsJoinedByString:@"0"]; - - NSLayoutConstraint *con = - [NSLayoutConstraint constraintWithItem:field - attribute:NSLayoutAttributeWidth - relatedBy:NSLayoutRelationEqual - toItem:nil - attribute:NSLayoutAttributeNotAnAttribute - multiplier:1.0 - constant:WidthFromString(fString)]; - [field addConstraint:con]; - [item setObject:con forKey:@"constrain"]; -} - -- (void) buttonAction:(NSButton *)sender { - const auto command = sender.tag; - - const auto active = Core::App().activeWindow(); - const auto controller = active ? active->sessionController() : nullptr; - Core::Sandbox::Instance().customEnterFromEventLoop([=] { - switch (command) { - case kCommandPlayPause: - Media::Player::instance()->playPause(kSongType); - break; - case kCommandPlaylistPrevious: - Media::Player::instance()->previous(kSongType); - break; - case kCommandPlaylistNext: - Media::Player::instance()->next(kSongType); - break; - case kCommandClosePlayer: - controller->content()->closeBothPlayers(); - break; - } - }); -} - -- (void) seekbarChanged:(NSSliderTouchBarItem *)sender { - // https://stackoverflow.com/a/45891017 - NSEvent *event = [[NSApplication sharedApplication] currentEvent]; - const auto touchUp = [event touchesMatchingPhase:NSTouchPhaseEnded inView:nil].count > 0; - Core::Sandbox::Instance().customEnterFromEventLoop([=] { - if (touchUp) { - Media::Player::instance()->finishSeeking(kSongType, sender.slider.doubleValue); - } else { - Media::Player::instance()->startSeeking(kSongType); - } - }); -} - -- (void) segmentClicked:(NSSegmentedControl *)sender { - const auto identifier = sender.selectedSegment - ? kScrubberEmojiItemIdentifier - : kScrubberStickersItemIdentifier; - _popoverPicker.popoverTouchBar.defaultItemIdentifiers = @[identifier]; - [_popoverPicker showPopover:nil]; -} - -- (void) formatterClicked:(NSSegmentedControl *)sender { - [[_touchBarMain itemForIdentifier:kPopoverInputItemIdentifier] - dismissPopover:nil]; - const auto command = int(sender.selectedSegment) + kCommandBold; - sender.selectedSegment = -1; - SendKeyEvent(command); -} - --(void)dealloc { - [_touchBarMain release]; - [_touchBarAudioPlayer release]; - [super dealloc]; -} - -@end // @implementation TouchBar - -#endif // OS_OSX