diff --git a/Telegram/Resources/art/sprite.png b/Telegram/Resources/art/sprite.png index 04c9fa5a8..e8c0edafc 100644 Binary files a/Telegram/Resources/art/sprite.png and b/Telegram/Resources/art/sprite.png differ diff --git a/Telegram/Resources/art/sprite_200x.png b/Telegram/Resources/art/sprite_200x.png index 91644529f..be3a0b00c 100644 Binary files a/Telegram/Resources/art/sprite_200x.png and b/Telegram/Resources/art/sprite_200x.png differ diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style index 9040e248e..9b919578a 100644 --- a/Telegram/Resources/basic.style +++ b/Telegram/Resources/basic.style @@ -272,36 +272,10 @@ solidScroll: flatScroll { duration: 150; hiding: 0; } +defaultDropdownDuration: 150; +defaultDropdownPadding: margins(10px, 10px, 10px, 10px); defaultDropdownShadow: icon {{ "dropdown_shadow", windowShadowFg }}; -defaultPopupMenu: PopupMenu { - skip: 5px; - - shadow: defaultDropdownShadow; - shadowShift: 1px; - - itemBg: white; - itemBgOver: overBg; - itemFg: black; - itemFgOver: black; - itemFgDisabled: #ccc; - itemFgShortcut: #999; - itemFgShortcutOver: #7c99b2; - itemFgShortcutDisabled: #ccc; - - itemPadding: margins(17px, 8px, 17px, 7px); - itemFont: normalFont; - - separatorPadding: margins(0px, 5px, 0px, 5px); - separatorWidth: 1px; - separatorFg: #f1f1f1; - - arrow: icon {{ "dropdown_submenu_arrow", #373737 }}; - - duration: 120; - - widthMin: 180px; - widthMax: 300px; -} +defaultDropdownShadowShift: 1px; defaultTooltip: Tooltip { textBg: #eef2f5; @@ -1088,54 +1062,6 @@ btnUnblock: flatButton(btnSend) { downColor: #db6352; } -btnAttachDocument: iconedButton(btnDefIconed) { - icon: sprite(218px, 68px, 24px, 24px); - iconPos: point(11px, 11px); - downIcon: sprite(218px, 68px, 24px, 24px); - downIconPos: point(11px, 12px); - - overBgColor: btnWhiteHover; - width: 46px; - height: 46px; -} -btnAttachPhoto: iconedButton(btnAttachDocument) { - icon: sprite(118px, 0px, 24px, 24px); - downIcon: sprite(118px, 0px, 24px, 24px); -} -btnAttachEmoji: iconedButton(btnAttachDocument) { - overBgColor: white; - icon: sprite(374px, 344px, 21px, 22px); - iconPos: point(6px, 12px); - downIcon: sprite(374px, 344px, 21px, 22px); - downIconPos: point(6px, 12px); - - width: 33px; -} -emojiCircle: size(19px, 19px); -emojiCirclePeriod: 1500; -emojiCircleDuration: 500; -emojiCircleTop: 13px; -emojiCircleLine: 2px; -emojiCircleFg: #b9b9b9; -emojiCirclePart: 3.5; -btnBotKbShow: iconedButton(btnAttachEmoji) { - icon: sprite(375px, 74px, 21px, 21px); - iconPos: point(6px, 12px); - downIcon: sprite(375px, 74px, 21px, 21px); - downIconPos: point(6px, 12px); -} -btnBotCmdStart: iconedButton(btnAttachEmoji) { - icon: sprite(354px, 74px, 21px, 21px); - iconPos: point(6px, 12px); - downIcon: sprite(354px, 74px, 21px, 21px); - downIconPos: point(6px, 12px); -} -btnBotKbHide: iconedButton(btnAttachEmoji) { - icon: sprite(373px, 95px, 23px, 14px); - iconPos: point(5px, 17px); - downIcon: sprite(373px, 95px, 23px, 14px); - downIconPos: point(5px, 17px); -} silentToggle: flatCheckbox { textColor: black; bgColor: white; @@ -1158,15 +1084,6 @@ silentToggle: flatCheckbox { imagePos: point(6px, 12px); } -btnRecordAudio: sprite(379px, 390px, 16px, 24px); -btnRecordAudioActive: sprite(379px, 366px, 16px, 24px); -recordSignalColor: #f17077; -recordSignalMin: 5px; -recordSignalMax: 12px; -recordCancel: #aaa; -recordCancelActive: #ec6466; -recordFont: font(13px); -recordTextTop: 14px; replySkip: 51px; replyColor: #377aae; @@ -1418,68 +1335,6 @@ connectingBG: #fffe; connectingColor: #777; connectingPadding: margins(5px, 5px, 5px, 5px); -dropdownDef: dropdown { - border: 1px; - borderColor: #ebebeb; - - padding: margins(10px, 10px, 10px, 10px); - shadow: defaultDropdownShadow; - shadowShift: 1px; - - duration: 150; - width: 0px; -} -defaultInnerDropdown: InnerDropdown { - padding: margins(10px, 10px, 10px, 10px); - shadow: defaultDropdownShadow; - shadowShift: 1px; - - duration: 150; -} - -dropdownAttachDocument: iconedButton(btnAttachDocument) { - iconPos: point(14px, 13px); - downIconPos: point(14px, 14px); - - width: 172px; - height: 49px; - - color: black; - - font: font(16px); - - textPos: point(50px, 13px); - downTextPos: point(50px, 14px); -} -dropdownAttachPhoto: iconedButton(dropdownAttachDocument) { - icon: sprite(118px, 0px, 24px, 24px); - downIcon: sprite(118px, 0px, 24px, 24px); -} -dropdownMediaPhotos: iconedButton(dropdownAttachPhoto) { - width: 200px; -} -dropdownMediaVideos: iconedButton(dropdownMediaPhotos) { - icon: sprite(92px, 348px, 24px, 24px); - downIcon: sprite(92px, 348px, 24px, 24px); -} -dropdownMediaSongs: iconedButton(dropdownMediaPhotos) { - icon: sprite(60px, 374px, 24px, 26px); - downIcon: sprite(60px, 374px, 24px, 26px); - iconPos: point(12px, 12px); - downIconPos: point(12px, 13px); -} -dropdownMediaDocuments: iconedButton(dropdownAttachDocument) { - width: 200px; -} -dropdownMediaAudios: iconedButton(dropdownMediaDocuments) { - icon: sprite(62px, 348px, 24px, 24px); - downIcon: sprite(62px, 348px, 24px, 24px); -} -dropdownMediaLinks: iconedButton(dropdownMediaDocuments) { - icon: sprite(372px, 414px, 24px, 24px); - downIcon: sprite(372px, 414px, 24px, 24px); -} - dragFont: font(28px semibold); dragSubfont: font(20px semibold); dragColor: #777; @@ -1720,49 +1575,6 @@ mvControlMargin: 0px; mvControlSize: 90px; mvIconSize: size(60px, 56px); -mvDropdown: dropdown(dropdownDef) { - shadow: icon {}; - padding: margins(11px, 12px, 11px, 12px); - - border: 0px; - width: 182px; -} -mvButton: iconedButton(btnDefIconed) { - bgColor: #383838; - overBgColor: #505050; - font: font(fsize); - - opacity: 1.; - overOpacity: 1.; - - width: -32px; - height: 36px; - - color: white; - - textPos: point(16px, 9px); - downTextPos: point(16px, 10px); - - duration: 0; -} -mvPopupMenu: PopupMenu(defaultPopupMenu) { - shadow: icon {}; - - itemBg: #383838; - itemBgOver: #505050; - itemFg: white; - itemFgOver: white; - itemFgDisabled: #999; - itemFgShortcut: #eee; - itemFgShortcutOver: #fff; - itemFgShortcutDisabled: #999; - - separatorFg: #484848; -} -mvContextButton: iconedButton(mvButton) { - bgColor: #383838E6; - overBgColor: #505050E7; -} mvWaitHide: 2000; mvHideDuration: 1000; mvShowDuration: 200; @@ -1818,23 +1630,6 @@ macAlwaysThisAppTop: 4; macAppHintTop: 8; macCautionIconSize: 16; -btnContext: iconedButton(btnDefIconed) { - bgColor: white; - overBgColor: btnWhiteHover; - font: font(14px); - - opacity: 1.; - overOpacity: 1.; - - width: -32px; - height: 36px; - - color: black; - - textPos: point(16px, 7px); - downTextPos: point(16px, 8px); -} - radialSize: size(50px, 50px); radialLine: 3px; radialDuration: 350; diff --git a/Telegram/Resources/basic_types.style b/Telegram/Resources/basic_types.style index f3baf7e69..3ade742e2 100644 --- a/Telegram/Resources/basic_types.style +++ b/Telegram/Resources/basic_types.style @@ -227,59 +227,6 @@ switcher { duration: int; } -dropdown { - border: pixels; - borderColor: color; - - padding: margins; - shadow: icon; - shadowShift: pixels; - - duration: int; - width: pixels; -} - -InnerDropdown { - padding: margins; - shadow: icon; - shadowShift: pixels; - - duration: int; - width: pixels; - - scrollMargin: margins; - scrollPadding: margins; -} - -PopupMenu { - skip: pixels; - - shadow: icon; - shadowShift: pixels; - - itemBg: color; - itemBgOver: color; - itemFg: color; - itemFgOver: color; - itemFgDisabled: color; - itemFgShortcut: color; - itemFgShortcutOver: color; - itemFgShortcutDisabled: color; - itemPadding: margins; - itemFont: font; - - separatorPadding: margins; - separatorWidth: pixels; - separatorFg: color; - - arrow: icon; - - duration: int; - - widthMin: pixels; - widthMax: pixels; -} - Tooltip { textBg: color; textFg: color; diff --git a/Telegram/Resources/icons/media_type_file.png b/Telegram/Resources/icons/media_type_file.png new file mode 100644 index 000000000..dcc7996f1 Binary files /dev/null and b/Telegram/Resources/icons/media_type_file.png differ diff --git a/Telegram/Resources/icons/media_type_file@2x.png b/Telegram/Resources/icons/media_type_file@2x.png new file mode 100644 index 000000000..7df85a0b2 Binary files /dev/null and b/Telegram/Resources/icons/media_type_file@2x.png differ diff --git a/Telegram/Resources/icons/media_type_link.png b/Telegram/Resources/icons/media_type_link.png new file mode 100644 index 000000000..02b47d0e9 Binary files /dev/null and b/Telegram/Resources/icons/media_type_link.png differ diff --git a/Telegram/Resources/icons/media_type_link@2x.png b/Telegram/Resources/icons/media_type_link@2x.png new file mode 100644 index 000000000..d10338d6e Binary files /dev/null and b/Telegram/Resources/icons/media_type_link@2x.png differ diff --git a/Telegram/Resources/icons/media_type_photo.png b/Telegram/Resources/icons/media_type_photo.png new file mode 100644 index 000000000..520c9b5d8 Binary files /dev/null and b/Telegram/Resources/icons/media_type_photo.png differ diff --git a/Telegram/Resources/icons/media_type_photo@2x.png b/Telegram/Resources/icons/media_type_photo@2x.png new file mode 100644 index 000000000..e2a1fa4df Binary files /dev/null and b/Telegram/Resources/icons/media_type_photo@2x.png differ diff --git a/Telegram/Resources/icons/media_type_song.png b/Telegram/Resources/icons/media_type_song.png new file mode 100644 index 000000000..da6d751ab Binary files /dev/null and b/Telegram/Resources/icons/media_type_song.png differ diff --git a/Telegram/Resources/icons/media_type_song@2x.png b/Telegram/Resources/icons/media_type_song@2x.png new file mode 100644 index 000000000..24c8128fd Binary files /dev/null and b/Telegram/Resources/icons/media_type_song@2x.png differ diff --git a/Telegram/Resources/icons/media_type_video.png b/Telegram/Resources/icons/media_type_video.png new file mode 100644 index 000000000..48a8321e1 Binary files /dev/null and b/Telegram/Resources/icons/media_type_video.png differ diff --git a/Telegram/Resources/icons/media_type_video@2x.png b/Telegram/Resources/icons/media_type_video@2x.png new file mode 100644 index 000000000..6366ba5b6 Binary files /dev/null and b/Telegram/Resources/icons/media_type_video@2x.png differ diff --git a/Telegram/Resources/icons/media_type_voice.png b/Telegram/Resources/icons/media_type_voice.png new file mode 100644 index 000000000..66deec6dd Binary files /dev/null and b/Telegram/Resources/icons/media_type_voice.png differ diff --git a/Telegram/Resources/icons/media_type_voice@2x.png b/Telegram/Resources/icons/media_type_voice@2x.png new file mode 100644 index 000000000..cdbdcadbc Binary files /dev/null and b/Telegram/Resources/icons/media_type_voice@2x.png differ diff --git a/Telegram/Resources/icons/send_control_bot_command.png b/Telegram/Resources/icons/send_control_bot_command.png new file mode 100644 index 000000000..f18a7c01a Binary files /dev/null and b/Telegram/Resources/icons/send_control_bot_command.png differ diff --git a/Telegram/Resources/icons/send_control_bot_command@2x.png b/Telegram/Resources/icons/send_control_bot_command@2x.png new file mode 100644 index 000000000..0db466a7c Binary files /dev/null and b/Telegram/Resources/icons/send_control_bot_command@2x.png differ diff --git a/Telegram/Resources/icons/send_control_bot_keyboard.png b/Telegram/Resources/icons/send_control_bot_keyboard.png new file mode 100644 index 000000000..329f8ef42 Binary files /dev/null and b/Telegram/Resources/icons/send_control_bot_keyboard.png differ diff --git a/Telegram/Resources/icons/send_control_bot_keyboard@2x.png b/Telegram/Resources/icons/send_control_bot_keyboard@2x.png new file mode 100644 index 000000000..43f0be684 Binary files /dev/null and b/Telegram/Resources/icons/send_control_bot_keyboard@2x.png differ diff --git a/Telegram/Resources/icons/send_control_bot_keyboard_hide.png b/Telegram/Resources/icons/send_control_bot_keyboard_hide.png new file mode 100644 index 000000000..6ada764b5 Binary files /dev/null and b/Telegram/Resources/icons/send_control_bot_keyboard_hide.png differ diff --git a/Telegram/Resources/icons/send_control_bot_keyboard_hide@2x.png b/Telegram/Resources/icons/send_control_bot_keyboard_hide@2x.png new file mode 100644 index 000000000..49ebaa0ba Binary files /dev/null and b/Telegram/Resources/icons/send_control_bot_keyboard_hide@2x.png differ diff --git a/Telegram/Resources/icons/send_control_emoji.png b/Telegram/Resources/icons/send_control_emoji.png new file mode 100644 index 000000000..65c52d50c Binary files /dev/null and b/Telegram/Resources/icons/send_control_emoji.png differ diff --git a/Telegram/Resources/icons/send_control_emoji@2x.png b/Telegram/Resources/icons/send_control_emoji@2x.png new file mode 100644 index 000000000..773350b00 Binary files /dev/null and b/Telegram/Resources/icons/send_control_emoji@2x.png differ diff --git a/Telegram/Resources/icons/send_control_record.png b/Telegram/Resources/icons/send_control_record.png new file mode 100644 index 000000000..8729b1fcb Binary files /dev/null and b/Telegram/Resources/icons/send_control_record.png differ diff --git a/Telegram/Resources/icons/send_control_record@2x.png b/Telegram/Resources/icons/send_control_record@2x.png new file mode 100644 index 000000000..7a45e6df2 Binary files /dev/null and b/Telegram/Resources/icons/send_control_record@2x.png differ diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index 11d57a58e..e6f3b9c05 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -28,7 +28,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "lang.h" #include "boxes/confirmbox.h" #include "ui/filedialog.h" -#include "ui/popupmenu.h" +#include "ui/widgets/tooltip.h" #include "langloaderplain.h" #include "localstorage.h" #include "autoupdater.h" @@ -926,7 +926,7 @@ void AppClass::onAppStateChanged(Qt::ApplicationState state) { _window->updateIsActive((state == Qt::ApplicationActive) ? Global::OnlineFocusTimeout() : Global::OfflineBlurTimeout()); } if (state != Qt::ApplicationActive) { - PopupTooltip::Hide(); + Ui::Tooltip::Hide(); } } diff --git a/Telegram/SourceFiles/boxes/abstractbox.cpp b/Telegram/SourceFiles/boxes/abstractbox.cpp index 6df51ba70..106b79d70 100644 --- a/Telegram/SourceFiles/boxes/abstractbox.cpp +++ b/Telegram/SourceFiles/boxes/abstractbox.cpp @@ -201,7 +201,7 @@ void ScrollableBox::resizeEvent(QResizeEvent *e) { AbstractBox::resizeEvent(e); } -void ScrollableBox::init(ScrolledWidget *inner, int bottomSkip, int topSkip) { +void ScrollableBox::init(TWidget *inner, int bottomSkip, int topSkip) { _bottomSkip = bottomSkip; _topSkip = topSkip; _scroll->setOwnedWidget(inner); diff --git a/Telegram/SourceFiles/boxes/abstractbox.h b/Telegram/SourceFiles/boxes/abstractbox.h index 4c6e28b1a..2f80e07ec 100644 --- a/Telegram/SourceFiles/boxes/abstractbox.h +++ b/Telegram/SourceFiles/boxes/abstractbox.h @@ -105,7 +105,7 @@ public: ScrollableBox(const style::flatScroll &scroll, int w = st::boxWideWidth); protected: - void init(ScrolledWidget *inner, int bottomSkip = st::boxScrollSkip, int topSkip = st::boxTitleHeight); + void init(TWidget *inner, int bottomSkip = st::boxScrollSkip, int topSkip = st::boxTitleHeight); void setScrollSkips(int bottomSkip = st::boxScrollSkip, int topSkip = st::boxTitleHeight); void resizeEvent(QResizeEvent *e) override; diff --git a/Telegram/SourceFiles/boxes/backgroundbox.cpp b/Telegram/SourceFiles/boxes/backgroundbox.cpp index 181ff3d49..ae9d61c94 100644 --- a/Telegram/SourceFiles/boxes/backgroundbox.cpp +++ b/Telegram/SourceFiles/boxes/backgroundbox.cpp @@ -54,7 +54,7 @@ void BackgroundBox::onBackgroundChosen(int index) { onClose(); } -BackgroundBox::Inner::Inner(QWidget *parent) : ScrolledWidget(parent) +BackgroundBox::Inner::Inner(QWidget *parent) : TWidget(parent) , _bgCount(0) , _rows(0) , _over(-1) diff --git a/Telegram/SourceFiles/boxes/backgroundbox.h b/Telegram/SourceFiles/boxes/backgroundbox.h index 798593658..500ad61d8 100644 --- a/Telegram/SourceFiles/boxes/backgroundbox.h +++ b/Telegram/SourceFiles/boxes/backgroundbox.h @@ -42,7 +42,7 @@ private: }; // This class is hold in header because it requires Qt preprocessing. -class BackgroundBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber { +class BackgroundBox::Inner : public TWidget, public RPCSender, private base::Subscriber { Q_OBJECT public: diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp index 8a5e09674..879935f68 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.cpp +++ b/Telegram/SourceFiles/boxes/contactsbox.cpp @@ -555,7 +555,7 @@ ContactsBox::Inner::ContactData::ContactData(PeerData *peer, base::lambda_wrap(st::contactsPhotoCheckbox, std_::move(updateCallback), PaintUserpicCallback(peer))) { } -ContactsBox::Inner::Inner(QWidget *parent, CreatingGroupType creating) : ScrolledWidget(parent) +ContactsBox::Inner::Inner(QWidget *parent, CreatingGroupType creating) : TWidget(parent) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _newItemHeight(creating == CreatingGroupNone ? st::contactsNewItemHeight : 0) , _creating(creating) @@ -565,7 +565,7 @@ ContactsBox::Inner::Inner(QWidget *parent, CreatingGroupType creating) : Scrolle init(); } -ContactsBox::Inner::Inner(QWidget *parent, ChannelData *channel, MembersFilter membersFilter, const MembersAlreadyIn &already) : ScrolledWidget(parent) +ContactsBox::Inner::Inner(QWidget *parent, ChannelData *channel, MembersFilter membersFilter, const MembersAlreadyIn &already) : TWidget(parent) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _channel(channel) , _membersFilter(membersFilter) @@ -583,7 +583,7 @@ namespace { } } -ContactsBox::Inner::Inner(QWidget *parent, ChatData *chat, MembersFilter membersFilter) : ScrolledWidget(parent) +ContactsBox::Inner::Inner(QWidget *parent, ChatData *chat, MembersFilter membersFilter) : TWidget(parent) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _chat(chat) , _membersFilter(membersFilter) @@ -615,7 +615,7 @@ void ContactsBox::Inner::addDialogsToList(FilterCallback callback) { } } -ContactsBox::Inner::Inner(QWidget *parent, UserData *bot) : ScrolledWidget(parent) +ContactsBox::Inner::Inner(QWidget *parent, UserData *bot) : TWidget(parent) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _bot(bot) , _allAdmins(this, lang(lng_chat_all_members_admins), false, st::contactsAdminCheckbox) diff --git a/Telegram/SourceFiles/boxes/contactsbox.h b/Telegram/SourceFiles/boxes/contactsbox.h index 0db72c0dd..c56f97b50 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.h +++ b/Telegram/SourceFiles/boxes/contactsbox.h @@ -132,7 +132,7 @@ private: }; // This class is hold in header because it requires Qt preprocessing. -class ContactsBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber { +class ContactsBox::Inner : public TWidget, public RPCSender, private base::Subscriber { Q_OBJECT public: diff --git a/Telegram/SourceFiles/boxes/members_box.cpp b/Telegram/SourceFiles/boxes/members_box.cpp index 8290a5bec..c5647ef29 100644 --- a/Telegram/SourceFiles/boxes/members_box.cpp +++ b/Telegram/SourceFiles/boxes/members_box.cpp @@ -97,7 +97,7 @@ void MembersBox::onAdminAdded() { _loadTimer.start(ReloadChannelMembersTimeout); } -MembersBox::Inner::Inner(QWidget *parent, ChannelData *channel, MembersFilter filter) : ScrolledWidget(parent) +MembersBox::Inner::Inner(QWidget *parent, ChannelData *channel, MembersFilter filter) : TWidget(parent) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _newItemHeight((channel->amCreator() && (channel->membersCount() < (channel->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax()) || (!channel->isMegagroup() && !channel->isPublic()) || filter == MembersFilter::Admins)) ? st::contactsNewItemHeight : 0) , _newItemSel(false) diff --git a/Telegram/SourceFiles/boxes/members_box.h b/Telegram/SourceFiles/boxes/members_box.h index f93f7a7b2..a9639610f 100644 --- a/Telegram/SourceFiles/boxes/members_box.h +++ b/Telegram/SourceFiles/boxes/members_box.h @@ -61,7 +61,7 @@ private: }; // This class is hold in header because it requires Qt preprocessing. -class MembersBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber { +class MembersBox::Inner : public TWidget, public RPCSender, private base::Subscriber { Q_OBJECT public: diff --git a/Telegram/SourceFiles/boxes/sessionsbox.cpp b/Telegram/SourceFiles/boxes/sessionsbox.cpp index 52003f6ff..7cd8d1e25 100644 --- a/Telegram/SourceFiles/boxes/sessionsbox.cpp +++ b/Telegram/SourceFiles/boxes/sessionsbox.cpp @@ -242,7 +242,7 @@ void SessionsBox::onTerminateAll() { } } -SessionsBox::Inner::Inner(QWidget *parent, SessionsBox::List *list, SessionsBox::Data *current) : ScrolledWidget(parent) +SessionsBox::Inner::Inner(QWidget *parent, SessionsBox::List *list, SessionsBox::Data *current) : TWidget(parent) , _list(list) , _current(current) , _terminating(0) diff --git a/Telegram/SourceFiles/boxes/sessionsbox.h b/Telegram/SourceFiles/boxes/sessionsbox.h index f591464f4..0021162cf 100644 --- a/Telegram/SourceFiles/boxes/sessionsbox.h +++ b/Telegram/SourceFiles/boxes/sessionsbox.h @@ -72,7 +72,7 @@ private: }; // This class is hold in header because it requires Qt preprocessing. -class SessionsBox::Inner : public ScrolledWidget, public RPCSender { +class SessionsBox::Inner : public TWidget, public RPCSender { Q_OBJECT public: diff --git a/Telegram/SourceFiles/boxes/sharebox.cpp b/Telegram/SourceFiles/boxes/sharebox.cpp index a0054c2d2..aa65bdc6c 100644 --- a/Telegram/SourceFiles/boxes/sharebox.cpp +++ b/Telegram/SourceFiles/boxes/sharebox.cpp @@ -289,7 +289,7 @@ void ShareBox::onScroll() { _inner->setVisibleTopBottom(scrollTop, scrollTop + scroll->height()); } -ShareBox::Inner::Inner(QWidget *parent, ShareBox::FilterCallback &&filterCallback) : ScrolledWidget(parent) +ShareBox::Inner::Inner(QWidget *parent, ShareBox::FilterCallback &&filterCallback) : TWidget(parent) , _filterCallback(std_::move(filterCallback)) , _chatsIndexed(std_::make_unique(Dialogs::SortMode::Add)) { _rowsTop = st::shareRowsTop; diff --git a/Telegram/SourceFiles/boxes/sharebox.h b/Telegram/SourceFiles/boxes/sharebox.h index 449c56242..413344ca5 100644 --- a/Telegram/SourceFiles/boxes/sharebox.h +++ b/Telegram/SourceFiles/boxes/sharebox.h @@ -113,7 +113,7 @@ private: }; // This class is hold in header because it requires Qt preprocessing. -class ShareBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber { +class ShareBox::Inner : public TWidget, public RPCSender, private base::Subscriber { Q_OBJECT public: diff --git a/Telegram/SourceFiles/boxes/stickers_box.cpp b/Telegram/SourceFiles/boxes/stickers_box.cpp index 907a5f9d8..03c13475e 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.cpp +++ b/Telegram/SourceFiles/boxes/stickers_box.cpp @@ -498,7 +498,7 @@ void StickersBox::showAll() { ItemListBox::showAll(); } -StickersBox::Inner::Inner(QWidget *parent, StickersBox::Section section) : ScrolledWidget(parent) +StickersBox::Inner::Inner(QWidget *parent, StickersBox::Section section) : TWidget(parent) , _section(section) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _a_shifting(animation(this, &Inner::step_shifting)) @@ -511,7 +511,7 @@ StickersBox::Inner::Inner(QWidget *parent, StickersBox::Section section) : Scrol setup(); } -StickersBox::Inner::Inner(QWidget *parent, const Stickers::Order &archivedIds) : ScrolledWidget(parent) +StickersBox::Inner::Inner(QWidget *parent, const Stickers::Order &archivedIds) : TWidget(parent) , _section(StickersBox::Section::ArchivedPart) , _archivedIds(archivedIds) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) diff --git a/Telegram/SourceFiles/boxes/stickers_box.h b/Telegram/SourceFiles/boxes/stickers_box.h index 3524a7db8..31bc703b1 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.h +++ b/Telegram/SourceFiles/boxes/stickers_box.h @@ -103,7 +103,7 @@ private: int32 stickerPacksCount(bool includeDisabledOfficial = false); // This class is hold in header because it requires Qt preprocessing. -class StickersBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber { +class StickersBox::Inner : public TWidget, public RPCSender, private base::Subscriber { Q_OBJECT public: diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index 3bf665a98..cc71ef5a8 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -147,7 +147,7 @@ void StickerSetBox::resizeEvent(QResizeEvent *e) { } } -StickerSetBox::Inner::Inner(QWidget *parent, const MTPInputStickerSet &set) : ScrolledWidget(parent) +StickerSetBox::Inner::Inner(QWidget *parent, const MTPInputStickerSet &set) : TWidget(parent) , _input(set) { switch (set.type()) { case mtpc_inputStickerSetID: _setId = set.c_inputStickerSetID().vid.v; _setAccess = set.c_inputStickerSetID().vaccess_hash.v; break; diff --git a/Telegram/SourceFiles/boxes/stickersetbox.h b/Telegram/SourceFiles/boxes/stickersetbox.h index 8252ab4ab..53932eff3 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.h +++ b/Telegram/SourceFiles/boxes/stickersetbox.h @@ -65,7 +65,7 @@ private: }; // This class is hold in header because it requires Qt preprocessing. -class StickerSetBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber { +class StickerSetBox::Inner : public TWidget, public RPCSender, private base::Subscriber { Q_OBJECT public: diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp index 53ea0b47d..c43a4b373 100644 --- a/Telegram/SourceFiles/core/click_handler_types.cpp +++ b/Telegram/SourceFiles/core/click_handler_types.cpp @@ -27,7 +27,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "core/qthelp_regex.h" #include "core/qthelp_url.h" #include "localstorage.h" -#include "ui/popupmenu.h" +#include "ui/widgets/tooltip.h" QString UrlClickHandler::copyToClipboardContextItemText() const { return lang(isEmail() ? lng_context_copy_email : lng_context_copy_link); @@ -64,7 +64,7 @@ QString tryConvertUrlToLocal(QString url) { } // namespace void UrlClickHandler::doOpen(QString url) { - PopupTooltip::Hide(); + Ui::Tooltip::Hide(); if (isEmail(url)) { QUrl u(qstr("mailto:") + url); diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index 55d84da20..eb9007f36 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -25,7 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "dialogs/dialogs_layout.h" #include "styles/style_dialogs.h" #include "ui/buttons/round_button.h" -#include "ui/popupmenu.h" +#include "ui/widgets/popup_menu.h" #include "data/data_drafts.h" #include "lang.h" #include "application.h" @@ -639,7 +639,7 @@ void DialogsInner::contextMenuEvent(QContextMenuEvent *e) { if (!history) return; _menuPeer = history->peer; - _menu = new PopupMenu(); + _menu = new Ui::PopupMenu(); _menu->addAction(lang((_menuPeer->isChat() || _menuPeer->isMegagroup()) ? lng_context_view_group : (_menuPeer->isUser() ? lng_context_view_profile : lng_context_view_channel)), this, SLOT(onContextProfile()))->setEnabled(true); _menu->addAction(lang(menuPeerMuted() ? lng_enable_notifications_from_tray : lng_disable_notifications_from_tray), this, SLOT(onContextToggleNotifications()))->setEnabled(true); _menu->addAction(lang(lng_profile_search_messages), this, SLOT(onContextSearch()))->setEnabled(true); diff --git a/Telegram/SourceFiles/dialogswidget.h b/Telegram/SourceFiles/dialogswidget.h index 7a6631c4f..930349072 100644 --- a/Telegram/SourceFiles/dialogswidget.h +++ b/Telegram/SourceFiles/dialogswidget.h @@ -30,10 +30,10 @@ class IndexedList; namespace Ui { class RoundButton; +class PopupMenu; } // namespace Ui class MainWidget; -class PopupMenu; enum DialogsSearchRequestType { DialogsSearchFromStart, @@ -230,7 +230,7 @@ private: PeerData *_menuPeer = nullptr; PeerData *_menuActionPeer = nullptr; - PopupMenu *_menu = nullptr; + Ui::PopupMenu *_menu = nullptr; }; diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp deleted file mode 100644 index 5876eeff9..000000000 --- a/Telegram/SourceFiles/dropdown.cpp +++ /dev/null @@ -1,456 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop version of Telegram messaging app, see https://telegram.org - -Telegram Desktop is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -It is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the copyright holders give permission -to link the code of portions of this program with the OpenSSL library. - -Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org -*/ -#include "stdafx.h" -#include "dropdown.h" - -#include "styles/style_stickers.h" -#include "boxes/confirmbox.h" -#include "boxes/stickersetbox.h" -#include "inline_bots/inline_bot_result.h" -#include "inline_bots/inline_bot_layout_item.h" -#include "dialogs/dialogs_layout.h" -#include "historywidget.h" -#include "localstorage.h" -#include "lang.h" -#include "mainwindow.h" -#include "apiwrap.h" -#include "mainwidget.h" - -Dropdown::Dropdown(QWidget *parent, const style::dropdown &st) : TWidget(parent) -, _st(st) -, _width(_st.width) -, a_opacity(0) -, _a_appearance(animation(this, &Dropdown::step_appearance)) -, _shadow(_st.shadow) { - resetButtons(); - - _hideTimer.setSingleShot(true); - connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideStart())); - - if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { - connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWndActiveChanged())); - } -} - -void Dropdown::ignoreShow(bool ignore) { - _ignore = ignore; -} - -void Dropdown::onWndActiveChanged() { - if (!App::wnd()->windowHandle()->isActive() && !isHidden()) { - leaveEvent(0); - } -} - -IconedButton *Dropdown::addButton(IconedButton *button) { - button->setParent(this); - - int32 nw = _st.padding.left() + _st.padding.right() + button->width(); - if (nw > _width) { - _width = nw; - for (int32 i = 0, l = _buttons.size(); i < l; ++i) _buttons[i]->resize(_width - _st.padding.left() - _st.padding.right(), _buttons[i]->height()); - } else { - button->resize(_width - _st.padding.left() - _st.padding.right(), button->height()); - } - if (!button->isHidden()) { - if (_height > _st.padding.top() + _st.padding.bottom()) { - _height += _st.border; - } - _height += button->height(); - } - _buttons.push_back(button); - connect(button, SIGNAL(stateChanged(int, ButtonStateChangeSource)), this, SLOT(buttonStateChanged(int, ButtonStateChangeSource))); - - resize(_width, _height); - - return button; -} - -void Dropdown::resetButtons() { - _width = qMax(_st.padding.left() + _st.padding.right(), int(_st.width)); - _height = _st.padding.top() + _st.padding.bottom(); - for (int32 i = 0, l = _buttons.size(); i < l; ++i) { - delete _buttons[i]; - } - _buttons.clear(); - resize(_width, _height); - - _selected = -1; -} - -void Dropdown::updateButtons() { - int32 top = _st.padding.top(), starttop = top; - for (Buttons::const_iterator i = _buttons.cbegin(), e = _buttons.cend(); i != e; ++i) { - if (!(*i)->isHidden()) { - (*i)->move(_st.padding.left(), top); - if ((*i)->width() != _width - _st.padding.left() - _st.padding.right()) { - (*i)->resize(_width - _st.padding.left() - _st.padding.right(), (*i)->height()); - } - top += (*i)->height() + _st.border; - } - } - _height = top + _st.padding.bottom() - (top > starttop ? _st.border : 0); - resize(_width, _height); -} - -void Dropdown::resizeEvent(QResizeEvent *e) { - int32 top = _st.padding.top(); - for (Buttons::const_iterator i = _buttons.cbegin(), e = _buttons.cend(); i != e; ++i) { - if (!(*i)->isHidden()) { - (*i)->move(_st.padding.left(), top); - top += (*i)->height() + _st.border; - } - } -} - -void Dropdown::paintEvent(QPaintEvent *e) { - Painter p(this); - - if (_a_appearance.animating()) { - p.setOpacity(a_opacity.current()); - } - - // draw shadow - QRect r(_st.padding.left(), _st.padding.top(), _width - _st.padding.left() - _st.padding.right(), _height - _st.padding.top() - _st.padding.bottom()); - _shadow.paint(p, r, _st.shadowShift); - - if (!_buttons.isEmpty() && _st.border > 0) { // paint separators - p.setPen(_st.borderColor->p); - int32 top = _st.padding.top(), i = 0, l = _buttons.size(); - for (; i < l; ++i) { - if (!_buttons.at(i)->isHidden()) break; - } - if (i < l) { - top += _buttons.at(i)->height(); - for (++i; i < l; ++i) { - if (!_buttons.at(i)->isHidden()) { - p.fillRect(_st.padding.left(), top, _width - _st.padding.left() - _st.padding.right(), _st.border, _st.borderColor->b); - top += _st.border + _buttons.at(i)->height(); - } - } - } - } -} - -void Dropdown::enterEvent(QEvent *e) { - _hideTimer.stop(); - if (_hiding) showStart(); - return TWidget::enterEvent(e); -} - -void Dropdown::leaveEvent(QEvent *e) { - if (_a_appearance.animating()) { - hideStart(); - } else { - _hideTimer.start(300); - } - return TWidget::leaveEvent(e); -} - -void Dropdown::keyPressEvent(QKeyEvent *e) { - if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { - if (_selected >= 0 && _selected < _buttons.size()) { - emit _buttons[_selected]->clicked(); - return; - } - } else if (e->key() == Qt::Key_Escape) { - hideStart(); - return; - } - if ((e->key() != Qt::Key_Up && e->key() != Qt::Key_Down) || _buttons.size() < 1) return; - - bool none = (_selected < 0 || _selected >= _buttons.size()); - int32 delta = (e->key() == Qt::Key_Down ? 1 : -1); - int32 newSelected = none ? (e->key() == Qt::Key_Down ? 0 : _buttons.size() - 1) : (_selected + delta); - if (newSelected < 0) { - newSelected = _buttons.size() - 1; - } else if (newSelected >= _buttons.size()) { - newSelected = 0; - } - int32 startFrom = newSelected; - while (_buttons.at(newSelected)->isHidden()) { - newSelected += delta; - if (newSelected < 0) { - newSelected = _buttons.size() - 1; - } else if (newSelected >= _buttons.size()) { - newSelected = 0; - } - if (newSelected == startFrom) return; - } - if (!none) { - _buttons[_selected]->setOver(false); - } - _selected = newSelected; - _buttons[_selected]->setOver(true); -} - -void Dropdown::buttonStateChanged(int oldState, ButtonStateChangeSource source) { - if (source == ButtonByUser) { - for (int32 i = 0, l = _buttons.size(); i < l; ++i) { - if (_buttons[i]->getState() & Button::StateOver) { - if (i != _selected) { - _buttons[i]->setOver(false); - } - } - } - } else if (source == ButtonByHover) { - bool found = false; - for (int32 i = 0, l = _buttons.size(); i < l; ++i) { - if (_buttons[i]->getState() & Button::StateOver) { - found = true; - if (i != _selected) { - int32 sel = _selected; - _selected = i; - if (sel >= 0 && sel < _buttons.size()) { - _buttons[sel]->setOver(false); - } - } - } - } - if (!found) { - _selected = -1; - } - } -} - -void Dropdown::otherEnter() { - _hideTimer.stop(); - showStart(); -} - -void Dropdown::otherLeave() { - if (_a_appearance.animating()) { - hideStart(); - } else { - _hideTimer.start(0); - } -} - -void Dropdown::fastHide() { - if (_a_appearance.animating()) { - _a_appearance.stop(); - } - a_opacity = anim::fvalue(0, 0); - _hideTimer.stop(); - hide(); -} - -void Dropdown::adjustButtons() { - for (Buttons::const_iterator i = _buttons.cbegin(), e = _buttons.cend(); i != e; ++i) { - (*i)->setOpacity(a_opacity.current()); - } -} - -void Dropdown::hideStart() { - _hiding = true; - a_opacity.start(0); - _a_appearance.start(); -} - -void Dropdown::hideFinish() { - emit hiding(); - hide(); - for (Buttons::const_iterator i = _buttons.cbegin(), e = _buttons.cend(); i != e; ++i) { - (*i)->clearState(); - } - _selected = -1; -} - -void Dropdown::showStart() { - if (!isHidden() && a_opacity.current() == 1) { - return; - } - _selected = -1; - _hiding = false; - show(); - a_opacity.start(1); - _a_appearance.start(); -} - -void Dropdown::step_appearance(float64 ms, bool timer) { - float64 dt = ms / _st.duration; - if (dt >= 1) { - _a_appearance.stop(); - a_opacity.finish(); - if (_hiding) { - hideFinish(); - } - } else { - a_opacity.update(dt, anim::linear); - } - adjustButtons(); - if (timer) update(); -} - -bool Dropdown::eventFilter(QObject *obj, QEvent *e) { - if (e->type() == QEvent::Enter) { - otherEnter(); - } else if (e->type() == QEvent::Leave) { - otherLeave(); - } else if (e->type() == QEvent::MouseButtonPress && static_cast(e)->button() == Qt::LeftButton) { - if (isHidden() || _hiding) { - otherEnter(); - } else { - otherLeave(); - } - } - return false; -} - -DragArea::DragArea(QWidget *parent) : TWidget(parent) -, _hiding(false) -, _in(false) -, a_opacity(0) -, a_color(st::dragColor->c) -, _a_appearance(animation(this, &DragArea::step_appearance)) -, _shadow(st::boxShadow) { - setMouseTracking(true); - setAcceptDrops(true); -} - -void DragArea::mouseMoveEvent(QMouseEvent *e) { - if (_hiding) return; - - bool newIn = QRect(st::dragPadding.left(), st::dragPadding.top(), width() - st::dragPadding.left() - st::dragPadding.right(), height() - st::dragPadding.top() - st::dragPadding.bottom()).contains(e->pos()); - if (newIn != _in) { - _in = newIn; - a_opacity.start(1); - a_color.start((_in ? st::dragDropColor : st::dragColor)->c); - _a_appearance.start(); - } -} - -void DragArea::dragMoveEvent(QDragMoveEvent *e) { - QRect r(st::dragPadding.left(), st::dragPadding.top(), width() - st::dragPadding.left() - st::dragPadding.right(), height() - st::dragPadding.top() - st::dragPadding.bottom()); - bool newIn = r.contains(e->pos()); - if (newIn != _in) { - _in = newIn; - a_opacity.start(1); - a_color.start((_in ? st::dragDropColor : st::dragColor)->c); - _a_appearance.start(); - } - e->setDropAction(_in ? Qt::CopyAction : Qt::IgnoreAction); - e->accept(); -} - -void DragArea::setText(const QString &text, const QString &subtext) { - _text = text; - _subtext = subtext; - update(); -} - -void DragArea::paintEvent(QPaintEvent *e) { - Painter p(this); - - if (_a_appearance.animating()) { - p.setOpacity(a_opacity.current()); - } - - QRect r(st::dragPadding.left(), st::dragPadding.top(), width() - st::dragPadding.left() - st::dragPadding.right(), height() - st::dragPadding.top() - st::dragPadding.bottom()); - - // draw shadow - _shadow.paint(p, r, st::boxShadowShift); - - p.fillRect(r, st::white->b); - - p.setPen(a_color.current()); - - p.setFont(st::dragFont->f); - p.drawText(QRect(0, (height() - st::dragHeight) / 2, width(), st::dragFont->height), _text, QTextOption(style::al_top)); - - p.setFont(st::dragSubfont->f); - p.drawText(QRect(0, (height() + st::dragHeight) / 2 - st::dragSubfont->height, width(), st::dragSubfont->height * 2), _subtext, QTextOption(style::al_top)); -} - -void DragArea::dragEnterEvent(QDragEnterEvent *e) { - static_cast(parentWidget())->dragEnterEvent(e); - e->setDropAction(Qt::IgnoreAction); - e->accept(); -} - -void DragArea::dragLeaveEvent(QDragLeaveEvent *e) { - static_cast(parentWidget())->dragLeaveEvent(e); - _in = false; - a_opacity.start(_hiding ? 0 : 1); - a_color.start((_in ? st::dragDropColor : st::dragColor)->c); - _a_appearance.start(); -} - -void DragArea::dropEvent(QDropEvent *e) { - static_cast(parentWidget())->dropEvent(e); - if (e->isAccepted()) { - emit dropped(e->mimeData()); - } -} - -void DragArea::otherEnter() { - showStart(); -} - -void DragArea::otherLeave() { - hideStart(); -} - -void DragArea::fastHide() { - if (_a_appearance.animating()) { - _a_appearance.stop(); - } - a_opacity = anim::fvalue(0, 0); - hide(); -} - -void DragArea::hideStart() { - _hiding = true; - _in = false; - a_opacity.start(0); - a_color.start((_in ? st::dragDropColor : st::dragColor)->c); - _a_appearance.start(); -} - -void DragArea::hideFinish() { - hide(); - _in = false; - a_color = anim::cvalue(st::dragColor->c); -} - -void DragArea::showStart() { - _hiding = false; - show(); - a_opacity.start(1); - a_color.start((_in ? st::dragDropColor : st::dragColor)->c); - _a_appearance.start(); -} - -void DragArea::step_appearance(float64 ms, bool timer) { - float64 dt = ms / st::dropdownDef.duration; - if (dt >= 1) { - a_opacity.finish(); - a_color.finish(); - if (_hiding) { - hideFinish(); - } - _a_appearance.stop(); - } else { - a_opacity.update(dt, anim::linear); - a_color.update(dt, anim::linear); - } - if (timer) update(); -} diff --git a/Telegram/SourceFiles/history/field_autocomplete.cpp b/Telegram/SourceFiles/history/field_autocomplete.cpp index 5ef830b47..bcae2de31 100644 --- a/Telegram/SourceFiles/history/field_autocomplete.cpp +++ b/Telegram/SourceFiles/history/field_autocomplete.cpp @@ -24,6 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwindow.h" #include "apiwrap.h" #include "localstorage.h" +#include "styles/style_history.h" FieldAutocomplete::FieldAutocomplete(QWidget *parent) : TWidget(parent) , _scroll(this, st::mentionScroll) @@ -333,7 +334,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) { void FieldAutocomplete::rowsUpdated(const internal::MentionRows &mrows, const internal::HashtagRows &hrows, const internal::BotCommandRows &brows, const StickerPack &srows, bool resetScroll) { if (mrows.isEmpty() && hrows.isEmpty() && brows.isEmpty() && srows.isEmpty()) { if (!isHidden()) { - hideStart(); + hideAnimated(); } _mrows.clear(); _hrows.clear(); @@ -354,7 +355,7 @@ void FieldAutocomplete::rowsUpdated(const internal::MentionRows &mrows, const in update(); if (hidden) { hide(); - showStart(); + showAnimated(); } } } @@ -394,7 +395,7 @@ void FieldAutocomplete::recount(bool resetScroll) { if (resetScroll) _inner->clearSel(); } -void FieldAutocomplete::fastHide() { +void FieldAutocomplete::hideFast() { if (_a_appearance.animating()) { _a_appearance.stop(); } @@ -403,18 +404,20 @@ void FieldAutocomplete::fastHide() { hideFinish(); } -void FieldAutocomplete::hideStart() { - if (!_hiding) { - if (_cache.isNull()) { - _scroll->show(); - _cache = myGrab(this); - } - _scroll->hide(); - _hiding = true; - a_opacity.start(0); - setAttribute(Qt::WA_OpaquePaintEvent, false); - _a_appearance.start(); +void FieldAutocomplete::hideAnimated() { + if (isHidden() || _hiding) { + return; } + + if (_cache.isNull()) { + _scroll->show(); + _cache = myGrab(this); + } + _scroll->hide(); + _hiding = true; + a_opacity.start(0); + setAttribute(Qt::WA_OpaquePaintEvent, false); + _a_appearance.start(); } void FieldAutocomplete::hideFinish() { @@ -424,7 +427,7 @@ void FieldAutocomplete::hideFinish() { _inner->clearSel(true); } -void FieldAutocomplete::showStart() { +void FieldAutocomplete::showAnimated() { if (!isHidden() && a_opacity.current() == 1 && !_hiding) { return; } @@ -441,7 +444,7 @@ void FieldAutocomplete::showStart() { } void FieldAutocomplete::step_appearance(float64 ms, bool timer) { - float64 dt = ms / st::dropdownDef.duration; + float64 dt = ms / st::defaultDropdownDuration; if (dt >= 1) { _a_appearance.stop(); a_opacity.finish(); @@ -544,7 +547,7 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) { int32 atwidth = st::mentionFont->width('@'), hashwidth = st::mentionFont->width('#'); int32 mentionleft = 2 * st::mentionPadding.left() + st::mentionPhotoSize; int32 mentionwidth = width() - mentionleft - 2 * st::mentionPadding.right(); - int32 htagleft = st::btnAttachPhoto.width + st::taMsgField.textMrg.left() - st::lineWidth, htagwidth = width() - st::mentionPadding.right() - htagleft - st::mentionScroll.width; + int32 htagleft = st::historyAttachPhoto.width + st::taMsgField.textMrg.left() - st::lineWidth, htagwidth = width() - st::mentionPadding.right() - htagleft - st::mentionScroll.width; if (!_srows->isEmpty()) { int32 rows = rowscount(_srows->size(), _stickersPerRow); @@ -650,7 +653,7 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) { } } - p.setFont(st::mentionFont->f); + p.setFont(st::mentionFont); if (!first.isEmpty()) { p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p); p.drawText(htagleft, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first); diff --git a/Telegram/SourceFiles/history/field_autocomplete.h b/Telegram/SourceFiles/history/field_autocomplete.h index d0579b16b..751035ffc 100644 --- a/Telegram/SourceFiles/history/field_autocomplete.h +++ b/Telegram/SourceFiles/history/field_autocomplete.h @@ -39,8 +39,6 @@ class FieldAutocomplete final : public TWidget { public: FieldAutocomplete(QWidget *parent); - void fastHide(); - bool clearFilteredBotCommands(); void showFiltered(PeerData *peer, QString query, bool addInlineBots); void showStickers(EmojiPtr emoji); @@ -75,6 +73,8 @@ public: return rect().contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); } + void hideFast(); + ~FieldAutocomplete(); signals: @@ -86,15 +86,15 @@ signals: void moderateKeyActivate(int key, bool *outHandled) const; public slots: - void hideStart(); - void hideFinish(); - - void showStart(); + void showAnimated(); + void hideAnimated(); protected: void paintEvent(QPaintEvent *e) override; private: + void hideFinish(); + void updateFiltered(bool resetScroll = false); void recount(bool resetScroll = false); diff --git a/Telegram/SourceFiles/history/history.style b/Telegram/SourceFiles/history/history.style index ae7d0e116..469119da6 100644 --- a/Telegram/SourceFiles/history/history.style +++ b/Telegram/SourceFiles/history/history.style @@ -20,6 +20,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ using "basic.style"; using "dialogs/dialogs.style"; +using "ui/widgets/widgets.style"; historyPaddingBottom: 10px; @@ -38,16 +39,16 @@ historyToDownBadgeSize: 22px; historyEmptyDog: icon {{ "history_empty_dog", #ffffff }}; historyEmptySize: 128px; -membersInnerScroll: flatScroll(solidScroll) { - deltat: 3px; - deltab: 3px; - round: 1px; - width: 8px; - deltax: 3px; -} membersInnerWidth: 310px; membersInnerHeightMax: 360px; membersInnerDropdown: InnerDropdown(defaultInnerDropdown) { + scroll: flatScroll(solidScroll) { + deltat: 3px; + deltab: 3px; + round: 1px; + width: 8px; + deltax: 3px; + } scrollMargin: margins(0px, 5px, 0px, 5px); scrollPadding: margins(0px, 3px, 8px, 3px); } @@ -138,3 +139,74 @@ historyPeer8NameFg: #ce671b; // orange historyPeer8UserpicBg: #f7b37c; historyPeer8UserpicFg: #de8d62; historyPeer8UserpicPerson: icon {{ size(120px, 120px), historyPeer8UserpicBg }, { "userpic_person", historyPeer8UserpicFg }}; + +historyMediaTypeFile: icon {{ "media_type_file", #b3b3b3, point(2px, 2px) }}; +historyMediaTypePhoto: icon {{ "media_type_photo", #bebebe, point(2px, 2px) }}; +historyMediaTypeVideo: icon {{ "media_type_video", #bebebe, point(2px, 2px) }}; +historyMediaTypeSong: icon {{ "media_type_song", #bebebe, point(0px, 0px) }}; +historyMediaTypeVoice: icon {{ "media_type_voice", #bebebe, point(2px, 2px) }}; +historyMediaTypeLink: icon {{ "media_type_link", #bebebe, point(2px, 2px) }}; + +historyAttachDocument: IconButton { + width: 46px; + height: 46px; + + opacity: 0.78; + overOpacity: 1.; + + icon: historyMediaTypeFile; + iconPosition: point(9px, 9px); + downIconPosition: point(9px, 10px); + + duration: 150; +} +historyAttachPhoto: IconButton(historyAttachDocument) { + icon: historyMediaTypePhoto; +} +historyAttachEmoji: IconButton(historyAttachDocument) { + width: 33px; + icon: icon {{ "send_control_emoji", #b9b9b9 }}; + iconPosition: point(12px, 16px); + downIconPosition: point(12px, 16px); +} +historyEmojiCircle: size(19px, 19px); +historyEmojiCirclePeriod: 1500; +historyEmojiCircleDuration: 500; +historyEmojiCircleTop: 13px; +historyEmojiCircleLine: 2px; +historyEmojiCircleFg: #b9b9b9; +historyEmojiCirclePart: 3.5; +historyBotKeyboardShow: IconButton(historyAttachEmoji) { + icon: icon {{ "send_control_bot_keyboard", #b3b3b3 }}; + iconPosition: point(6px, 12px); + downIconPosition: point(6px, 12px); +} +historyBotKeyboardHide: IconButton(historyAttachEmoji) { + icon: icon {{ "send_control_bot_keyboard_hide", #b3b3b3 }}; + iconPosition: point(5px, 17px); + downIconPosition: point(5px, 17px); +} +historyBotCommandStart: IconButton(historyBotKeyboardShow) { + icon: icon {{ "send_control_bot_command", #b3b3b3 }}; +} +historyRecordVoice: icon {{ "send_control_record", #b9b9b9 }}; +historyRecordVoiceActive: icon {{ "send_control_record", #58b2ed }}; +historyRecordSignalColor: #f17077; +historyRecordSignalMin: 5px; +historyRecordSignalMax: 12px; +historyRecordCancel: #aaa; +historyRecordCancelActive: #ec6466; +historyRecordFont: font(13px); +historyRecordTextTop: 14px; + +historyAttachDropdownMenu: DropdownMenu(defaultDropdownMenu) { + menu: Menu(defaultMenu) { + skip: 5px; + + itemBgOver: btnWhiteHover; + itemIconPosition: point(12px, 6px); + itemIconOpacity: 0.78; + itemIconOverOpacity: 1.; + itemPadding: margins(48px, 11px, 48px, 11px); + } +} diff --git a/Telegram/SourceFiles/history/history_drag_area.cpp b/Telegram/SourceFiles/history/history_drag_area.cpp new file mode 100644 index 000000000..d3aa25800 --- /dev/null +++ b/Telegram/SourceFiles/history/history_drag_area.cpp @@ -0,0 +1,175 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "history/history_drag_area.h" + +#include "styles/style_stickers.h" +#include "boxes/confirmbox.h" +#include "boxes/stickersetbox.h" +#include "inline_bots/inline_bot_result.h" +#include "inline_bots/inline_bot_layout_item.h" +#include "dialogs/dialogs_layout.h" +#include "historywidget.h" +#include "localstorage.h" +#include "lang.h" +#include "mainwindow.h" +#include "apiwrap.h" +#include "mainwidget.h" + +DragArea::DragArea(QWidget *parent) : TWidget(parent) +, _hiding(false) +, _in(false) +, a_opacity(0) +, a_color(st::dragColor->c) +, _a_appearance(animation(this, &DragArea::step_appearance)) +, _shadow(st::boxShadow) { + setMouseTracking(true); + setAcceptDrops(true); +} + +void DragArea::mouseMoveEvent(QMouseEvent *e) { + if (_hiding) return; + + bool newIn = QRect(st::dragPadding.left(), st::dragPadding.top(), width() - st::dragPadding.left() - st::dragPadding.right(), height() - st::dragPadding.top() - st::dragPadding.bottom()).contains(e->pos()); + if (newIn != _in) { + _in = newIn; + a_opacity.start(1); + a_color.start((_in ? st::dragDropColor : st::dragColor)->c); + _a_appearance.start(); + } +} + +void DragArea::dragMoveEvent(QDragMoveEvent *e) { + QRect r(st::dragPadding.left(), st::dragPadding.top(), width() - st::dragPadding.left() - st::dragPadding.right(), height() - st::dragPadding.top() - st::dragPadding.bottom()); + bool newIn = r.contains(e->pos()); + if (newIn != _in) { + _in = newIn; + a_opacity.start(1); + a_color.start((_in ? st::dragDropColor : st::dragColor)->c); + _a_appearance.start(); + } + e->setDropAction(_in ? Qt::CopyAction : Qt::IgnoreAction); + e->accept(); +} + +void DragArea::setText(const QString &text, const QString &subtext) { + _text = text; + _subtext = subtext; + update(); +} + +void DragArea::paintEvent(QPaintEvent *e) { + Painter p(this); + + if (_a_appearance.animating()) { + p.setOpacity(a_opacity.current()); + } + + QRect r(st::dragPadding.left(), st::dragPadding.top(), width() - st::dragPadding.left() - st::dragPadding.right(), height() - st::dragPadding.top() - st::dragPadding.bottom()); + + // draw shadow + _shadow.paint(p, r, st::boxShadowShift); + + p.fillRect(r, st::white->b); + + p.setPen(a_color.current()); + + p.setFont(st::dragFont->f); + p.drawText(QRect(0, (height() - st::dragHeight) / 2, width(), st::dragFont->height), _text, QTextOption(style::al_top)); + + p.setFont(st::dragSubfont->f); + p.drawText(QRect(0, (height() + st::dragHeight) / 2 - st::dragSubfont->height, width(), st::dragSubfont->height * 2), _subtext, QTextOption(style::al_top)); +} + +void DragArea::dragEnterEvent(QDragEnterEvent *e) { + static_cast(parentWidget())->dragEnterEvent(e); + e->setDropAction(Qt::IgnoreAction); + e->accept(); +} + +void DragArea::dragLeaveEvent(QDragLeaveEvent *e) { + static_cast(parentWidget())->dragLeaveEvent(e); + _in = false; + a_opacity.start(_hiding ? 0 : 1); + a_color.start((_in ? st::dragDropColor : st::dragColor)->c); + _a_appearance.start(); +} + +void DragArea::dropEvent(QDropEvent *e) { + static_cast(parentWidget())->dropEvent(e); + if (e->isAccepted()) { + emit dropped(e->mimeData()); + } +} + +void DragArea::otherEnter() { + showStart(); +} + +void DragArea::otherLeave() { + hideStart(); +} + +void DragArea::hideFast() { + if (_a_appearance.animating()) { + _a_appearance.stop(); + } + a_opacity = anim::fvalue(0, 0); + hide(); +} + +void DragArea::hideStart() { + _hiding = true; + _in = false; + a_opacity.start(0); + a_color.start((_in ? st::dragDropColor : st::dragColor)->c); + _a_appearance.start(); +} + +void DragArea::hideFinish() { + hide(); + _in = false; + a_color = anim::cvalue(st::dragColor->c); +} + +void DragArea::showStart() { + _hiding = false; + show(); + a_opacity.start(1); + a_color.start((_in ? st::dragDropColor : st::dragColor)->c); + _a_appearance.start(); +} + +void DragArea::step_appearance(float64 ms, bool timer) { + float64 dt = ms / st::defaultDropdownDuration; + if (dt >= 1) { + a_opacity.finish(); + a_color.finish(); + if (_hiding) { + hideFinish(); + } + _a_appearance.stop(); + } else { + a_opacity.update(dt, anim::linear); + a_color.update(dt, anim::linear); + } + if (timer) update(); +} diff --git a/Telegram/SourceFiles/dropdown.h b/Telegram/SourceFiles/history/history_drag_area.h similarity index 59% rename from Telegram/SourceFiles/dropdown.h rename to Telegram/SourceFiles/history/history_drag_area.h index a9ed3f699..003bd9548 100644 --- a/Telegram/SourceFiles/dropdown.h +++ b/Telegram/SourceFiles/history/history_drag_area.h @@ -23,78 +23,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "ui/twidget.h" #include "ui/effects/rect_shadow.h" -class Dropdown : public TWidget { - Q_OBJECT - -public: - Dropdown(QWidget *parent, const style::dropdown &st = st::dropdownDef); - - IconedButton *addButton(IconedButton *button); - void resetButtons(); - void updateButtons(); - - void resizeEvent(QResizeEvent *e); - void paintEvent(QPaintEvent *e); - - void enterEvent(QEvent *e); - void leaveEvent(QEvent *e); - void keyPressEvent(QKeyEvent *e); - void otherEnter(); - void otherLeave(); - - void fastHide(); - void ignoreShow(bool ignore = true); - - void step_appearance(float64 ms, bool timer); - - bool eventFilter(QObject *obj, QEvent *e); - - bool overlaps(const QRect &globalRect) { - if (isHidden() || _a_appearance.animating()) return false; - - return QRect(_st.padding.left(), - _st.padding.top(), - _width - _st.padding.left() - _st.padding.right(), - _height - _st.padding.top() - _st.padding.bottom() - ).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); - } - -signals: - void hiding(); - -public slots: - void hideStart(); - void hideFinish(); - - void showStart(); - void onWndActiveChanged(); - - void buttonStateChanged(int oldState, ButtonStateChangeSource source); - -private: - void adjustButtons(); - - bool _ignore = false; - - typedef QVector Buttons; - Buttons _buttons; - - int32 _selected = -1; - - const style::dropdown &_st; - - int32 _width, _height; - bool _hiding = false; - - anim::fvalue a_opacity; - Animation _a_appearance; - - QTimer _hideTimer; - - Ui::RectShadow _shadow; - -}; - class DragArea : public TWidget { Q_OBJECT @@ -106,8 +34,6 @@ public: void otherEnter(); void otherLeave(); - void fastHide(); - void step_appearance(float64 ms, bool timer); bool overlaps(const QRect &globalRect) { @@ -120,6 +46,8 @@ public: ).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); } + void hideFast(); + protected: void paintEvent(QPaintEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 0473b6fcf..e48517114 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -29,17 +29,19 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "ui/filedialog.h" #include "ui/toast/toast.h" #include "ui/buttons/history_down_button.h" -#include "ui/inner_dropdown.h" +#include "ui/buttons/icon_button.h" +#include "ui/widgets/inner_dropdown.h" +#include "ui/widgets/dropdown_menu.h" #include "inline_bots/inline_bot_result.h" #include "data/data_drafts.h" #include "history/history_service_layout.h" #include "history/history_media_types.h" +#include "history/history_drag_area.h" #include "profile/profile_members_widget.h" #include "core/click_handler_types.h" #include "stickers/emoji_pan.h" #include "lang.h" #include "application.h" -#include "dropdown.h" #include "mainwidget.h" #include "mainwindow.h" #include "passcodewidget.h" @@ -52,6 +54,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "window/chat_background.h" #include "observer_peer.h" #include "core/qthelp_regex.h" +#include "ui/widgets/popup_menu.h" namespace { @@ -1154,7 +1157,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { isUponSelected = hasSelected; } - _menu = new PopupMenu(); + _menu = new Ui::PopupMenu(); _contextMenuLnk = ClickHandler::getActive(); HistoryItem *item = App::hoveredItem() ? App::hoveredItem() : App::hoveredLinkItem(); @@ -1698,7 +1701,7 @@ void HistoryInner::toggleScrollDateShown() { _scrollDateShown = !_scrollDateShown; auto from = _scrollDateShown ? 0. : 1.; auto to = _scrollDateShown ? 1. : 0.; - _scrollDateOpacity.start([this] { repaintScrollDateCallback(); }, from, to, st::btnAttachEmoji.duration); + _scrollDateOpacity.start([this] { repaintScrollDateCallback(); }, from, to, st::historyAttachPhoto.duration); } void HistoryInner::repaintScrollDateCallback() { @@ -1749,7 +1752,7 @@ void HistoryInner::leaveEvent(QEvent *e) { App::hoveredItem(nullptr); } ClickHandler::clearActive(); - PopupTooltip::Hide(); + Ui::Tooltip::Hide(); if (!ClickHandler::getPressed() && _cursor != style::cur_default) { _cursor = style::cur_default; setCursor(_cursor); @@ -1970,7 +1973,7 @@ void HistoryInner::onUpdateSelected() { dragState = item->getState(m.x(), m.y(), request); lnkhost = item; if (!dragState.link && m.x() >= st::msgMargin.left() && m.x() < st::msgMargin.left() + st::msgPhotoSize) { - if (HistoryMessage *msg = item->toHistoryMessage()) { + if (auto msg = item->toHistoryMessage()) { if (msg->hasFromPhoto()) { enumerateUserpics([&dragState, &lnkhost, &point](HistoryMessage *message, int userpicTop) -> bool { // stop enumeration if the userpic is above our point @@ -1992,10 +1995,10 @@ void HistoryInner::onUpdateSelected() { } bool lnkChanged = ClickHandler::setActive(dragState.link, lnkhost); if (lnkChanged || dragState.cursor != _dragCursorState) { - PopupTooltip::Hide(); + Ui::Tooltip::Hide(); } if (dragState.link || dragState.cursor == HistoryInDateCursorState || dragState.cursor == HistoryInForwardedCursorState) { - PopupTooltip::Show(1000, this); + Ui::Tooltip::Show(1000, this); } Qt::CursorShape cur = style::cur_default; @@ -2611,7 +2614,7 @@ void BotKeyboard::updateStyle(int newWidth) { void BotKeyboard::clearSelection() { if (_impl) { if (ClickHandler::setActive(ClickHandlerPtr(), this)) { - PopupTooltip::Hide(); + Ui::Tooltip::Hide(); setCursor(style::cur_default); } } @@ -2629,7 +2632,7 @@ QString BotKeyboard::tooltipText() const { } void BotKeyboard::updateSelected() { - PopupTooltip::Show(1000, this); + Ui::Tooltip::Show(1000, this); if (!_impl) return; @@ -2638,7 +2641,7 @@ void BotKeyboard::updateSelected() { auto link = _impl->getState(p.x() - x, p.y() - _st->margin); if (ClickHandler::setActive(link, this)) { - PopupTooltip::Hide(); + Ui::Tooltip::Hide(); setCursor(link ? style::cur_pointer : style::cur_default); } } @@ -2917,19 +2920,19 @@ SilentToggle::SilentToggle(QWidget *parent) : FlatCheckbox(parent, QString(), fa void SilentToggle::mouseMoveEvent(QMouseEvent *e) { FlatCheckbox::mouseMoveEvent(e); if (rect().contains(e->pos())) { - PopupTooltip::Show(1000, this); + Ui::Tooltip::Show(1000, this); } else { - PopupTooltip::Hide(); + Ui::Tooltip::Hide(); } } void SilentToggle::leaveEvent(QEvent *e) { - PopupTooltip::Hide(); + Ui::Tooltip::Hide(); } void SilentToggle::mouseReleaseEvent(QMouseEvent *e) { FlatCheckbox::mouseReleaseEvent(e); - PopupTooltip::Show(0, this); + Ui::Tooltip::Show(0, this); PeerData *p = App::main() ? App::main()->peer() : nullptr; if (p && p->isChannel() && p->notify != UnknownNotifySettings) { App::main()->updateNotifySetting(p, NotifySettingDontChange, checked() ? SilentNotifiesSetSilent : SilentNotifiesSetNotify); @@ -2991,20 +2994,20 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent) , _botStart(this, lang(lng_bot_start), st::btnSend) , _joinChannel(this, lang(lng_channel_join), st::btnSend) , _muteUnmute(this, lang(lng_channel_mute), st::btnSend) -, _attachDocument(this, st::btnAttachDocument) -, _attachPhoto(this, st::btnAttachPhoto) -, _attachEmoji(this, st::btnAttachEmoji) -, _kbShow(this, st::btnBotKbShow) -, _kbHide(this, st::btnBotKbHide) -, _cmdStart(this, st::btnBotCmdStart) +, _attachDocument(this, st::historyAttachDocument) +, _attachPhoto(this, st::historyAttachPhoto) +, _attachEmoji(this, st::historyAttachEmoji) +, _botKeyboardShow(this, st::historyBotKeyboardShow) +, _botKeyboardHide(this, st::historyBotKeyboardHide) +, _botCommandStart(this, st::historyBotCommandStart) , _silent(this) , _field(this, st::taMsgField, lang(lng_message_ph)) , _a_record(animation(this, &HistoryWidget::step_record)) , _a_recording(animation(this, &HistoryWidget::step_recording)) -, a_recordCancel(st::recordCancel->c, st::recordCancel->c) -, _recordCancelWidth(st::recordFont->width(lang(lng_record_cancel))) +, a_recordCancel(st::historyRecordCancel->c, st::historyRecordCancel->c) +, _recordCancelWidth(st::historyRecordFont->width(lang(lng_record_cancel))) , _kbScroll(this, st::botKbScroll) -, _attachType(this) +, _attachType(this, st::historyAttachDropdownMenu) , _emojiPan(this) , _attachDragDocument(this) , _attachDragPhoto(this) @@ -3028,8 +3031,8 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent) connect(&_joinChannel, SIGNAL(clicked()), this, SLOT(onJoinChannel())); connect(&_muteUnmute, SIGNAL(clicked()), this, SLOT(onMuteUnmute())); connect(&_silent, SIGNAL(clicked()), this, SLOT(onBroadcastSilentChange())); - connect(&_attachDocument, SIGNAL(clicked()), this, SLOT(onDocumentSelect())); - connect(&_attachPhoto, SIGNAL(clicked()), this, SLOT(onPhotoSelect())); + connect(_attachDocument, SIGNAL(clicked()), this, SLOT(onDocumentSelect())); + connect(_attachPhoto, SIGNAL(clicked()), this, SLOT(onPhotoSelect())); connect(&_field, SIGNAL(submitted(bool)), this, SLOT(onSend(bool))); connect(&_field, SIGNAL(cancelled()), this, SLOT(onCancel())); connect(&_field, SIGNAL(tabbed()), this, SLOT(onFieldTabbed())); @@ -3107,24 +3110,24 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent) _reportSpamPanel.move(0, 0); _reportSpamPanel.hide(); - _attachDocument.hide(); - _attachPhoto.hide(); - _attachEmoji.hide(); - _kbShow.hide(); - _kbHide.hide(); + _attachDocument->hide(); + _attachPhoto->hide(); + _attachEmoji->hide(); + _botKeyboardShow->hide(); + _botKeyboardHide->hide(); _silent.hide(); - _cmdStart.hide(); + _botCommandStart->hide(); - _attachDocument.installEventFilter(_attachType); - _attachPhoto.installEventFilter(_attachType); - _attachEmoji.installEventFilter(_emojiPan); + _attachDocument->installEventFilter(_attachType); + _attachPhoto->installEventFilter(_attachType); + _attachEmoji->installEventFilter(_emojiPan); - connect(&_kbShow, SIGNAL(clicked()), this, SLOT(onKbToggle())); - connect(&_kbHide, SIGNAL(clicked()), this, SLOT(onKbToggle())); - connect(&_cmdStart, SIGNAL(clicked()), this, SLOT(onCmdStart())); + connect(_botKeyboardShow, SIGNAL(clicked()), this, SLOT(onKbToggle())); + connect(_botKeyboardHide, SIGNAL(clicked()), this, SLOT(onKbToggle())); + connect(_botCommandStart, SIGNAL(clicked()), this, SLOT(onCmdStart())); - connect(_attachType->addButton(new IconedButton(this, st::dropdownAttachDocument, lang(lng_attach_file))), SIGNAL(clicked()), this, SLOT(onDocumentSelect())); - connect(_attachType->addButton(new IconedButton(this, st::dropdownAttachPhoto, lang(lng_attach_photo))), SIGNAL(clicked()), this, SLOT(onPhotoSelect())); + _attachType->addAction(lang(lng_attach_file), this, SLOT(onDocumentSelect()), &st::historyMediaTypeFile); + _attachType->addAction(lang(lng_attach_photo), this, SLOT(onPhotoSelect()), &st::historyMediaTypePhoto); _attachType->hide(); _emojiPan->hide(); _attachDragDocument->hide(); @@ -3221,7 +3224,7 @@ void HistoryWidget::applyInlineBotQuery(UserData *bot, const QString &query) { _emojiPan->queryInlineBot(_inlineBot, _peer, query); } if (!_fieldAutocomplete->isHidden()) { - _fieldAutocomplete->hideStart(); + _fieldAutocomplete->hideAnimated(); } } else { clearInlineBot(); @@ -3267,7 +3270,7 @@ void HistoryWidget::onTextChange() { _a_record.stop(); _inRecord = _inField = false; a_recordOver = a_recordDown = anim::fvalue(0, 0); - a_recordCancel = anim::cvalue(st::recordCancel->c, st::recordCancel->c); + a_recordCancel = anim::cvalue(st::historyRecordCancel->c, st::historyRecordCancel->c); } } if (updateCmdStartShown()) { @@ -3541,7 +3544,7 @@ void HistoryWidget::notify_botCommandsChanged(UserData *user) { } void HistoryWidget::notify_inlineBotRequesting(bool requesting) { - _attachEmoji.setLoading(requesting); + _attachEmoji->setLoading(requesting); } void HistoryWidget::notify_replyMarkupUpdated(const HistoryItem *item) { @@ -4497,14 +4500,14 @@ void HistoryWidget::updateControlsVisibility() { _fieldAutocomplete->hide(); _field.hide(); _fieldBarCancel.hide(); - _attachDocument.hide(); - _attachPhoto.hide(); - _attachEmoji.hide(); + _attachDocument->hide(); + _attachPhoto->hide(); + _attachEmoji->hide(); _silent.hide(); _historyToEnd->hide(); - _kbShow.hide(); - _kbHide.hide(); - _cmdStart.hide(); + _botKeyboardShow->hide(); + _botKeyboardHide->hide(); + _botCommandStart->hide(); _attachType->hide(); _emojiPan->hide(); if (_pinnedBar) { @@ -4556,17 +4559,17 @@ void HistoryWidget::updateControlsVisibility() { _send.hide(); if (_inlineBotCancel) _inlineBotCancel->hide(); _botStart.hide(); - _attachDocument.hide(); - _attachPhoto.hide(); + _attachDocument->hide(); + _attachPhoto->hide(); _silent.hide(); _kbScroll.hide(); _fieldBarCancel.hide(); - _attachDocument.hide(); - _attachPhoto.hide(); - _attachEmoji.hide(); - _kbShow.hide(); - _kbHide.hide(); - _cmdStart.hide(); + _attachDocument->hide(); + _attachPhoto->hide(); + _attachEmoji->hide(); + _botKeyboardShow->hide(); + _botKeyboardHide->hide(); + _botCommandStart->hide(); _attachType->hide(); _emojiPan->hide(); if (!_field.isHidden()) { @@ -4588,12 +4591,12 @@ void HistoryWidget::updateControlsVisibility() { _send.hide(); if (_inlineBotCancel) _inlineBotCancel->hide(); _field.hide(); - _attachEmoji.hide(); - _kbShow.hide(); - _kbHide.hide(); - _cmdStart.hide(); - _attachDocument.hide(); - _attachPhoto.hide(); + _attachEmoji->hide(); + _botKeyboardShow->hide(); + _botKeyboardHide->hide(); + _botCommandStart->hide(); + _attachDocument->hide(); + _attachPhoto->hide(); _silent.hide(); _kbScroll.hide(); _fieldBarCancel.hide(); @@ -4618,12 +4621,12 @@ void HistoryWidget::updateControlsVisibility() { } if (_recording) { _field.hide(); - _attachEmoji.hide(); - _kbShow.hide(); - _kbHide.hide(); - _cmdStart.hide(); - _attachDocument.hide(); - _attachPhoto.hide(); + _attachEmoji->hide(); + _botKeyboardShow->hide(); + _botKeyboardHide->hide(); + _botCommandStart->hide(); + _attachDocument->hide(); + _attachPhoto->hide(); _silent.hide(); if (_kbShown) { _kbScroll.show(); @@ -4634,38 +4637,38 @@ void HistoryWidget::updateControlsVisibility() { _field.show(); if (_kbShown) { _kbScroll.show(); - _attachEmoji.hide(); - _kbHide.show(); - _kbShow.hide(); - _cmdStart.hide(); + _attachEmoji->hide(); + _botKeyboardHide->show(); + _botKeyboardShow->hide(); + _botCommandStart->hide(); } else if (_kbReplyTo) { _kbScroll.hide(); - _attachEmoji.show(); - _kbHide.hide(); - _kbShow.hide(); - _cmdStart.hide(); + _attachEmoji->show(); + _botKeyboardHide->hide(); + _botKeyboardShow->hide(); + _botCommandStart->hide(); } else { _kbScroll.hide(); - _attachEmoji.show(); - _kbHide.hide(); + _attachEmoji->show(); + _botKeyboardHide->hide(); if (_keyboard.hasMarkup()) { - _kbShow.show(); - _cmdStart.hide(); + _botKeyboardShow->show(); + _botCommandStart->hide(); } else { - _kbShow.hide(); + _botKeyboardShow->hide(); if (_cmdStartShown) { - _cmdStart.show(); + _botCommandStart->show(); } else { - _cmdStart.hide(); + _botCommandStart->hide(); } } } if (cDefaultAttach() == dbidaPhoto) { - _attachDocument.hide(); - _attachPhoto.show(); + _attachDocument->hide(); + _attachPhoto->show(); } else { - _attachDocument.show(); - _attachPhoto.hide(); + _attachDocument->show(); + _attachPhoto->hide(); } if (hasSilentToggle()) { _silent.show(); @@ -4692,17 +4695,17 @@ void HistoryWidget::updateControlsVisibility() { _botStart.hide(); _joinChannel.hide(); _muteUnmute.hide(); - _attachDocument.hide(); - _attachPhoto.hide(); + _attachDocument->hide(); + _attachPhoto->hide(); _silent.hide(); _kbScroll.hide(); _fieldBarCancel.hide(); - _attachDocument.hide(); - _attachPhoto.hide(); - _attachEmoji.hide(); - _kbShow.hide(); - _kbHide.hide(); - _cmdStart.hide(); + _attachDocument->hide(); + _attachPhoto->hide(); + _attachEmoji->hide(); + _botKeyboardShow->hide(); + _botKeyboardHide->hide(); + _botCommandStart->hide(); _attachType->hide(); _emojiPan->hide(); _kbScroll.hide(); @@ -5219,6 +5222,12 @@ bool HistoryWidget::saveEditMsgFail(History *history, const RPCError &error, mtp return true; } +void HistoryWidget::hideSelectorControlsAnimated() { + _fieldAutocomplete->hideAnimated(); + _attachType->hideAnimated(); + _emojiPan->hideAnimated(); +} + void HistoryWidget::onSend(bool ctrlShiftEnter, MsgId replyTo) { if (!_history) return; @@ -5244,9 +5253,7 @@ void HistoryWidget::onSend(bool ctrlShiftEnter, MsgId replyTo) { _saveDraftStart = getms(); onDraftSave(); - if (!_fieldAutocomplete->isHidden()) _fieldAutocomplete->hideStart(); - if (!_attachType->isHidden()) _attachType->hideStart(); - if (!_emojiPan->isHidden()) _emojiPan->hideStart(); + hideSelectorControlsAnimated(); if (replyTo < 0) cancelReply(lastKeyboardUsed); if (_previewData && _previewData->pendingTill) previewCancel(); @@ -5448,14 +5455,14 @@ void HistoryWidget::showAnimated(Window::SlideDirection direction, const Window: _kbScroll.hide(); _reportSpamPanel.hide(); _historyToEnd->hide(); - _attachDocument.hide(); - _attachPhoto.hide(); - _attachEmoji.hide(); + _attachDocument->hide(); + _attachPhoto->hide(); + _attachEmoji->hide(); _fieldAutocomplete->hide(); _silent.hide(); - _kbShow.hide(); - _kbHide.hide(); - _cmdStart.hide(); + _botKeyboardShow->hide(); + _botKeyboardHide->hide(); + _botCommandStart->hide(); _field.hide(); _fieldBarCancel.hide(); _send.hide(); @@ -5565,16 +5572,16 @@ void HistoryWidget::step_recording(float64 ms, bool timer) { } else { a_recordingLevel.update(dt, anim::linear); } - if (timer) update(_attachDocument.geometry()); + if (timer) update(_attachDocument->geometry()); } void HistoryWidget::onPhotoSelect() { if (!_history) return; - _attachDocument.clearState(); - _attachDocument.hide(); - _attachPhoto.show(); - _attachType->fastHide(); + _attachDocument->clearState(); + _attachDocument->hide(); + _attachPhoto->show(); + _attachType->hideFast(); if (cDefaultAttach() != dbidaPhoto) { cSetDefaultAttach(dbidaPhoto); @@ -5599,10 +5606,10 @@ void HistoryWidget::onPhotoSelect() { void HistoryWidget::onDocumentSelect() { if (!_history) return; - _attachPhoto.clearState(); - _attachPhoto.hide(); - _attachDocument.show(); - _attachType->fastHide(); + _attachPhoto->clearState(); + _attachPhoto->hide(); + _attachDocument->show(); + _attachType->hideFast(); if (cDefaultAttach() != dbidaDocument) { cSetDefaultAttach(dbidaDocument); @@ -5671,7 +5678,7 @@ void HistoryWidget::mouseMoveEvent(QMouseEvent *e) { _inField = inField; a_recordOver.restart(); a_recordDown.start(_inField ? 1 : 0); - a_recordCancel.start(_inField ? st::recordCancel->c : st::recordCancelActive->c); + a_recordCancel.start(_inField ? st::historyRecordCancel->c : st::historyRecordCancelActive->c); startAnim = true; } if (inReplyEdit != _inReplyEdit) { @@ -5722,7 +5729,7 @@ void HistoryWidget::stopRecording(bool send) { a_recordDown.start(0); a_recordOver.restart(); - a_recordCancel = anim::cvalue(st::recordCancel->c, st::recordCancel->c); + a_recordCancel = anim::cvalue(st::historyRecordCancel->c, st::historyRecordCancel->c); _a_record.start(); } @@ -5960,7 +5967,7 @@ void HistoryWidget::updateDragAreas() { case DragStateFiles: _attachDragDocument->otherEnter(); _attachDragDocument->setText(lang(lng_drag_files_here), lang(lng_drag_to_send_files)); - _attachDragPhoto->fastHide(); + _attachDragPhoto->hideFast(); break; case DragStatePhotoFiles: _attachDragDocument->otherEnter(); @@ -5969,7 +5976,7 @@ void HistoryWidget::updateDragAreas() { _attachDragPhoto->setText(lang(lng_drag_photos_here), lang(lng_drag_to_send_quick)); break; case DragStateImage: - _attachDragDocument->fastHide(); + _attachDragDocument->hideFast(); _attachDragPhoto->otherEnter(); _attachDragPhoto->setText(lang(lng_drag_images_here), lang(lng_drag_to_send_quick)); break; @@ -6127,10 +6134,10 @@ void HistoryWidget::onFilesDrop(const QMimeData *data) { void HistoryWidget::onKbToggle(bool manual) { auto fieldEnabled = canWriteMessage(); if (_kbShown || _kbReplyTo) { - _kbHide.hide(); + _botKeyboardHide->hide(); if (_kbShown) { if (fieldEnabled) { - _kbShow.show(); + _botKeyboardShow->show(); } if (manual && _history) { _history->lastKeyboardHiddenId = _keyboard.forMsgId().msg; @@ -6154,10 +6161,10 @@ void HistoryWidget::onKbToggle(bool manual) { } } } else if (!_keyboard.hasMarkup() && _keyboard.forceReply()) { - _kbHide.hide(); - _kbShow.hide(); + _botKeyboardHide->hide(); + _botKeyboardShow->hide(); if (fieldEnabled) { - _cmdStart.show(); + _botCommandStart->show(); } _kbScroll.hide(); _kbShown = false; @@ -6175,8 +6182,8 @@ void HistoryWidget::onKbToggle(bool manual) { _history->lastKeyboardHiddenId = 0; } } else if (fieldEnabled) { - _kbHide.show(); - _kbShow.hide(); + _botKeyboardHide->show(); + _botKeyboardShow->hide(); _kbScroll.show(); _kbShown = true; @@ -6195,10 +6202,10 @@ void HistoryWidget::onKbToggle(bool manual) { } } resizeEvent(0); - if (_kbHide.isHidden() && canWriteMessage()) { - _attachEmoji.show(); + if (_botKeyboardHide->isHidden() && canWriteMessage()) { + _attachEmoji->show(); } else { - _attachEmoji.hide(); + _attachEmoji->hide(); } updateField(); } @@ -6310,13 +6317,13 @@ void HistoryWidget::setMembersShowAreaActive(bool active) { void HistoryWidget::onMembersDropdownShow() { if (!_membersDropdown) { - _membersDropdown.create(this, st::membersInnerDropdown, st::membersInnerScroll); + _membersDropdown.create(this, st::membersInnerDropdown); _membersDropdown->setOwnedWidget(new Profile::MembersWidget(_membersDropdown, _peer, Profile::MembersWidget::TitleVisibility::Hidden)); - _membersDropdown->resize(st::membersInnerWidth, _membersDropdown->height()); + _membersDropdown->resizeToWidth(st::membersInnerWidth); _membersDropdown->setMaxHeight(countMembersDropdownHeightMax()); _membersDropdown->moveToLeft(0, 0); - connect(_membersDropdown, SIGNAL(hidden()), this, SLOT(onMembersDropdownHidden())); + connect(_membersDropdown, SIGNAL(beforeHidden()), this, SLOT(onMembersDropdownHidden())); } _membersDropdown->otherEnter(); } @@ -6436,24 +6443,24 @@ void HistoryWidget::moveFieldControls() { // (_attachDocument|_attachPhoto) _field (_silent|_cmdStart|_kbShow) (_kbHide|_attachEmoji) [_broadcast] _send // (_botStart|_unblock|_joinChannel|_muteUnmute) - int buttonsBottom = bottom - _attachDocument.height(); - _attachDocument.move(0, buttonsBottom); - _attachPhoto.move(0, buttonsBottom); - _field.move(_attachDocument.width(), bottom - _field.height() - st::sendPadding); + int buttonsBottom = bottom - _attachDocument->height(); + _attachDocument->move(0, buttonsBottom); + _attachPhoto->move(0, buttonsBottom); + _field.move(_attachDocument->width(), bottom - _field.height() - st::sendPadding); _send.move(right - _send.width(), buttonsBottom); if (_inlineBotCancel) _inlineBotCancel->move(_send.pos()); right -= _send.width(); - _attachEmoji.move(right - _attachEmoji.width(), buttonsBottom); - _kbHide.move(right - _kbHide.width(), buttonsBottom); - right -= _attachEmoji.width(); - _kbShow.move(right - _kbShow.width(), buttonsBottom); - _cmdStart.move(right - _cmdStart.width(), buttonsBottom); + _attachEmoji->move(right - _attachEmoji->width(), buttonsBottom); + _botKeyboardHide->move(right - _botKeyboardHide->width(), buttonsBottom); + right -= _attachEmoji->width(); + _botKeyboardShow->move(right - _botKeyboardShow->width(), buttonsBottom); + _botCommandStart->move(right - _botCommandStart->width(), buttonsBottom); _silent.move(right - _silent.width(), buttonsBottom); right = w; _fieldBarCancel.move(right - _fieldBarCancel.width(), _field.y() - st::sendPadding - _fieldBarCancel.height()); - _attachType->move(0, _attachDocument.y() - _attachType->height()); - _emojiPan->moveBottom(_attachEmoji.y()); + _attachType->move(0, _attachDocument->y() - _attachType->height()); + _emojiPan->moveBottom(_attachEmoji->y()); _botStart.setGeometry(0, bottom - _botStart.height(), w, _botStart.height()); _unblock.setGeometry(0, bottom - _unblock.height(), w, _unblock.height()); @@ -6463,11 +6470,11 @@ void HistoryWidget::moveFieldControls() { void HistoryWidget::updateFieldSize() { bool kbShowShown = _history && !_kbShown && _keyboard.hasMarkup(); - int fieldWidth = width() - _attachDocument.width(); + int fieldWidth = width() - _attachDocument->width(); fieldWidth -= _send.width(); - fieldWidth -= _attachEmoji.width(); - if (kbShowShown) fieldWidth -= _kbShow.width(); - if (_cmdStartShown) fieldWidth -= _cmdStart.width(); + fieldWidth -= _attachEmoji->width(); + if (kbShowShown) fieldWidth -= _botKeyboardShow->width(); + if (_cmdStartShown) fieldWidth -= _botCommandStart->width(); if (hasSilentToggle()) fieldWidth -= _silent.width(); if (_field.width() != fieldWidth) { @@ -6493,7 +6500,7 @@ void HistoryWidget::inlineBotChanged() { _inlineBotCancel = std_::make_unique(this, st::inlineBotCancel); connect(_inlineBotCancel.get(), SIGNAL(clicked()), this, SLOT(onInlineBotCancel())); _inlineBotCancel->setGeometry(_send.geometry()); - _attachEmoji.raise(); + _attachEmoji->raise(); updateFieldSubmitSettings(); updateControlsVisibility(); } else if (!isInlineBot && _inlineBotCancel) { @@ -7029,7 +7036,7 @@ void HistoryWidget::updateControlsGeometry() { _historyToEnd->moveToRight(st::historyToDownPosition.x(), _scroll.y() + _scroll.height() - _historyToEnd->height() - st::historyToDownPosition.y()); - _emojiPan->setMaxHeight(height() - st::dropdownDef.padding.top() - st::dropdownDef.padding.bottom() - _attachEmoji.height()); + _emojiPan->setMaxHeight(height() - st::defaultDropdownPadding.top() - st::defaultDropdownPadding.bottom() - _attachEmoji->height()); if (_membersDropdown) { _membersDropdown->setMaxHeight(countMembersDropdownHeightMax()); } @@ -7315,15 +7322,15 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { if (!_a_show.animating()) { if (hasMarkup) { _kbScroll.show(); - _attachEmoji.hide(); - _kbHide.show(); + _attachEmoji->hide(); + _botKeyboardHide->show(); } else { _kbScroll.hide(); - _attachEmoji.show(); - _kbHide.hide(); + _attachEmoji->show(); + _botKeyboardHide->hide(); } - _kbShow.hide(); - _cmdStart.hide(); + _botKeyboardShow->hide(); + _botCommandStart->hide(); } int32 maxh = hasMarkup ? qMin(_keyboard.height(), int(st::maxFieldHeight) - (int(st::maxFieldHeight) / 2)) : 0; _field.setMaxHeight(st::maxFieldHeight - maxh); @@ -7338,10 +7345,10 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { } else { if (!_a_show.animating()) { _kbScroll.hide(); - _attachEmoji.show(); - _kbHide.hide(); - _kbShow.show(); - _cmdStart.hide(); + _attachEmoji->show(); + _botKeyboardHide->hide(); + _botKeyboardShow->show(); + _botCommandStart->hide(); } _field.setMaxHeight(st::maxFieldHeight); _kbShown = false; @@ -7354,10 +7361,10 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { } else { if (!_scroll.isHidden()) { _kbScroll.hide(); - _attachEmoji.show(); - _kbHide.hide(); - _kbShow.hide(); - _cmdStart.show(); + _attachEmoji->show(); + _botKeyboardHide->hide(); + _botKeyboardShow->hide(); + _botCommandStart->show(); } _field.setMaxHeight(st::maxFieldHeight); _kbShown = false; @@ -7543,9 +7550,7 @@ void HistoryWidget::onInlineResultSend(InlineBots::Result *result, UserData *bot Local::writeRecentHashtagsAndBots(); } - if (!_fieldAutocomplete->isHidden()) _fieldAutocomplete->hideStart(); - if (!_attachType->isHidden()) _attachType->hideStart(); - if (!_emojiPan->isHidden()) _emojiPan->hideStart(); + hideSelectorControlsAnimated(); _field.setFocus(); } @@ -7711,9 +7716,7 @@ bool HistoryWidget::sendExistingDocument(DocumentData *doc, const QString &capti onCloudDraftSave(); // won't be needed if SendInlineBotResult will clear the cloud draft } - if (!_fieldAutocomplete->isHidden()) _fieldAutocomplete->hideStart(); - if (!_attachType->isHidden()) _attachType->hideStart(); - if (!_emojiPan->isHidden()) _emojiPan->hideStart(); + hideSelectorControlsAnimated(); _field.setFocus(); return true; @@ -7758,9 +7761,7 @@ void HistoryWidget::sendExistingPhoto(PhotoData *photo, const QString &caption) App::historyRegRandom(randomId, newId); - if (!_fieldAutocomplete->isHidden()) _fieldAutocomplete->hideStart(); - if (!_attachType->isHidden()) _attachType->hideStart(); - if (!_emojiPan->isHidden()) _emojiPan->hideStart(); + hideSelectorControlsAnimated(); _field.setFocus(); } @@ -8001,7 +8002,7 @@ void HistoryWidget::cancelReplyAfterMediaSend(bool lastKeyboardUsed) { int HistoryWidget::countMembersDropdownHeightMax() const { int result = height() - st::membersInnerDropdown.padding.top() - st::membersInnerDropdown.padding.bottom(); - result -= _attachEmoji.height(); + result -= _attachEmoji->height(); accumulate_min(result, st::membersInnerHeightMax); return result; } @@ -8221,7 +8222,7 @@ void HistoryWidget::onCancel() { onFieldBarCancel(); } } else if (!_fieldAutocomplete->isHidden()) { - _fieldAutocomplete->hideStart(); + _fieldAutocomplete->hideAnimated(); } else { App::main()->showBackFromStack(); emit cancelled(); @@ -8637,36 +8638,36 @@ void HistoryWidget::paintEditHeader(Painter &p, const QRect &rect, int left, int void HistoryWidget::drawRecordButton(Painter &p) { if (a_recordDown.current() < 1) { - p.setOpacity(st::btnAttachEmoji.opacity * (1 - a_recordOver.current()) + st::btnAttachEmoji.overOpacity * a_recordOver.current()); - p.drawSprite(_send.x() + (_send.width() - st::btnRecordAudio.pxWidth()) / 2, _send.y() + (_send.height() - st::btnRecordAudio.pxHeight()) / 2, st::btnRecordAudio); + p.setOpacity(st::historyAttachEmoji.opacity * (1 - a_recordOver.current()) + st::historyAttachEmoji.overOpacity * a_recordOver.current()); + st::historyRecordVoice.paint(p, _send.x() + (_send.width() - st::historyRecordVoice.width()) / 2, _send.y() + (_send.height() - st::historyRecordVoice.height()) / 2, width()); } if (a_recordDown.current() > 0) { p.setOpacity(a_recordDown.current()); - p.drawSprite(_send.x() + (_send.width() - st::btnRecordAudioActive.pxWidth()) / 2, _send.y() + (_send.height() - st::btnRecordAudioActive.pxHeight()) / 2, st::btnRecordAudioActive); + st::historyRecordVoiceActive.paint(p, _send.x() + (_send.width() - st::historyRecordVoiceActive.width()) / 2, _send.y() + (_send.height() - st::historyRecordVoiceActive.height()) / 2, width()); } p.setOpacity(1); } void HistoryWidget::drawRecording(Painter &p) { p.setPen(Qt::NoPen); - p.setBrush(st::recordSignalColor->b); + p.setBrush(st::historyRecordSignalColor); p.setRenderHint(QPainter::HighQualityAntialiasing); float64 delta = qMin(float64(a_recordingLevel.current()) / 0x4000, 1.); - int32 d = 2 * qRound(st::recordSignalMin + (delta * (st::recordSignalMax - st::recordSignalMin))); - p.drawEllipse(_attachPhoto.x() + (_attachEmoji.width() - d) / 2, _attachPhoto.y() + (_attachPhoto.height() - d) / 2, d, d); + int32 d = 2 * qRound(st::historyRecordSignalMin + (delta * (st::historyRecordSignalMax - st::historyRecordSignalMin))); + p.drawEllipse(_attachPhoto->x() + (_attachEmoji->width() - d) / 2, _attachPhoto->y() + (_attachPhoto->height() - d) / 2, d, d); p.setRenderHint(QPainter::HighQualityAntialiasing, false); QString duration = formatDurationText(_recordingSamples / AudioVoiceMsgFrequency); - p.setFont(st::recordFont->f); + p.setFont(st::historyRecordFont); - p.setPen(st::black->p); - p.drawText(_attachPhoto.x() + _attachEmoji.width(), _attachPhoto.y() + st::recordTextTop + st::recordFont->ascent, duration); + p.setPen(st::black); + p.drawText(_attachPhoto->x() + _attachEmoji->width(), _attachPhoto->y() + st::historyRecordTextTop + st::historyRecordFont->ascent, duration); - int32 left = _attachPhoto.x() + _attachEmoji.width() + st::recordFont->width(duration) + ((_send.width() - st::btnRecordAudio.pxWidth()) / 2); + int32 left = _attachPhoto->x() + _attachEmoji->width() + st::historyRecordFont->width(duration) + ((_send.width() - st::historyRecordVoice.width()) / 2); int32 right = width() - _send.width(); p.setPen(a_recordCancel.current()); - p.drawText(left + (right - left - _recordCancelWidth) / 2, _attachPhoto.y() + st::recordTextTop + st::recordFont->ascent, lang(lng_record_cancel)); + p.drawText(left + (right - left - _recordCancelWidth) / 2, _attachPhoto->y() + st::historyRecordTextTop + st::historyRecordFont->ascent, lang(lng_record_cancel)); } void HistoryWidget::drawPinnedBar(Painter &p) { diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index f9906c92d..d5a81ba86 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -22,7 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "localimageloader.h" #include "ui/effects/rect_shadow.h" -#include "ui/popupmenu.h" +#include "ui/widgets/tooltip.h" #include "history/history_common.h" #include "history/field_autocomplete.h" #include "window/section_widget.h" @@ -36,17 +36,20 @@ class Result; } // namespace InlineBots namespace Ui { -class HistoryDownButton; class InnerDropdown; +class DropdownMenu; class PlainShadow; +class PopupMenu; +class IconButton; +class HistoryDownButton; +class EmojiButton; } // namespace Ui -class Dropdown; class DragArea; class EmojiPan; class HistoryWidget; -class HistoryInner : public TWidget, public AbstractTooltipShower, private base::Subscriber { +class HistoryInner : public TWidget, public Ui::AbstractTooltipShower, private base::Subscriber { Q_OBJECT public: @@ -256,7 +259,7 @@ private: QTimer _touchScrollTimer; // context menu - PopupMenu *_menu = nullptr; + Ui::PopupMenu *_menu = nullptr; // save visible area coords for painting / pressing userpics int _visibleAreaTop = 0; @@ -351,7 +354,7 @@ private: }; -class BotKeyboard : public TWidget, public AbstractTooltipShower, public ClickHandlerHost { +class BotKeyboard : public TWidget, public Ui::AbstractTooltipShower, public ClickHandlerHost { Q_OBJECT public: @@ -505,7 +508,7 @@ private: }; -class SilentToggle : public FlatCheckbox, public AbstractTooltipShower { +class SilentToggle : public FlatCheckbox, public Ui::AbstractTooltipShower { public: SilentToggle(QWidget *parent); @@ -868,6 +871,7 @@ private: void cancelReplyAfterMediaSend(bool lastKeyboardUsed); + void hideSelectorControlsAnimated(); int countMembersDropdownHeightMax() const; MsgId _replyToId = 0; @@ -1086,9 +1090,12 @@ private: FlatButton _send, _unblock, _botStart, _joinChannel, _muteUnmute; mtpRequestId _unblockRequest = 0; mtpRequestId _reportSpamRequest = 0; - IconedButton _attachDocument, _attachPhoto; - EmojiButton _attachEmoji; - IconedButton _kbShow, _kbHide, _cmdStart; + ChildWidget _attachDocument; + ChildWidget _attachPhoto; + ChildWidget _attachEmoji; + ChildWidget _botKeyboardShow; + ChildWidget _botKeyboardHide; + ChildWidget _botCommandStart; SilentToggle _silent; bool _cmdStartShown = false; MessageField _field; @@ -1115,7 +1122,7 @@ private: ChildWidget _membersDropdown = { nullptr }; QTimer _membersDropdownShowTimer; - ChildWidget _attachType; + ChildWidget _attachType; ChildWidget _emojiPan; DragState _attachDrag = DragStateNone; ChildWidget _attachDragDocument, _attachDragPhoto; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 550216a01..83e0ac535 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -22,6 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwidget.h" #include "styles/style_dialogs.h" +#include "styles/style_history.h" #include "ui/buttons/peer_avatar_button.h" #include "ui/buttons/round_button.h" #include "ui/widgets/shadow.h" @@ -29,7 +30,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "window/section_widget.h" #include "window/top_bar_widget.h" #include "data/data_drafts.h" -#include "dropdown.h" +#include "ui/widgets/dropdown_menu.h" #include "observer_peer.h" #include "apiwrap.h" #include "dialogswidget.h" @@ -75,7 +76,7 @@ MainWidget::MainWidget(MainWindow *window) : TWidget(window) , _topBar(this) , _playerPlaylist(this, Media::Player::Panel::Layout::OnlyPlaylist) , _playerPanel(this, Media::Player::Panel::Layout::Full) -, _mediaType(this) +, _mediaType(this, st::historyAttachDropdownMenu) , _api(new ApiWrap(this)) { setGeometry(QRect(0, st::titleHeight, App::wnd()->width(), App::wnd()->height() - st::titleHeight)); @@ -1382,16 +1383,16 @@ void MainWidget::mediaOverviewUpdated(PeerData *peer, MediaOverviewType type) { } } if (mask != _mediaTypeMask) { - _mediaType->resetButtons(); + _mediaType->clearActions(); for (int32 i = 0; i < OverviewCount; ++i) { if (mask & (1 << i)) { switch (i) { - case OverviewPhotos: connect(_mediaType->addButton(new IconedButton(this, st::dropdownMediaPhotos, lang(lng_media_type_photos))), SIGNAL(clicked()), this, SLOT(onPhotosSelect())); break; - case OverviewVideos: connect(_mediaType->addButton(new IconedButton(this, st::dropdownMediaVideos, lang(lng_media_type_videos))), SIGNAL(clicked()), this, SLOT(onVideosSelect())); break; - case OverviewMusicFiles: connect(_mediaType->addButton(new IconedButton(this, st::dropdownMediaSongs, lang(lng_media_type_songs))), SIGNAL(clicked()), this, SLOT(onSongsSelect())); break; - case OverviewFiles: connect(_mediaType->addButton(new IconedButton(this, st::dropdownMediaDocuments, lang(lng_media_type_files))), SIGNAL(clicked()), this, SLOT(onDocumentsSelect())); break; - case OverviewVoiceFiles: connect(_mediaType->addButton(new IconedButton(this, st::dropdownMediaAudios, lang(lng_media_type_audios))), SIGNAL(clicked()), this, SLOT(onAudiosSelect())); break; - case OverviewLinks: connect(_mediaType->addButton(new IconedButton(this, st::dropdownMediaLinks, lang(lng_media_type_links))), SIGNAL(clicked()), this, SLOT(onLinksSelect())); break; + case OverviewPhotos: _mediaType->addAction(lang(lng_media_type_photos), this, SLOT(onPhotosSelect()), &st::historyMediaTypePhoto); break; + case OverviewVideos: _mediaType->addAction(lang(lng_media_type_videos), this, SLOT(onVideosSelect()), &st::historyMediaTypeVideo); break; + case OverviewMusicFiles: _mediaType->addAction(lang(lng_media_type_songs), this, SLOT(onSongsSelect()), &st::historyMediaTypeSong); break; + case OverviewFiles: _mediaType->addAction(lang(lng_media_type_files), this, SLOT(onDocumentsSelect()), &st::historyMediaTypeFile); break; + case OverviewVoiceFiles: _mediaType->addAction(lang(lng_media_type_audios), this, SLOT(onAudiosSelect()), &st::historyMediaTypeVoice); break; + case OverviewLinks: _mediaType->addAction(lang(lng_media_type_links), this, SLOT(onLinksSelect()), &st::historyMediaTypeLink); break; } } } @@ -2912,32 +2913,32 @@ void MainWidget::setMembersShowAreaActive(bool active) { void MainWidget::onPhotosSelect() { if (_overview) _overview->switchType(OverviewPhotos); - _mediaType->hideStart(); + _mediaType->hideAnimated(); } void MainWidget::onVideosSelect() { if (_overview) _overview->switchType(OverviewVideos); - _mediaType->hideStart(); + _mediaType->hideAnimated(); } void MainWidget::onSongsSelect() { if (_overview) _overview->switchType(OverviewMusicFiles); - _mediaType->hideStart(); + _mediaType->hideAnimated(); } void MainWidget::onDocumentsSelect() { if (_overview) _overview->switchType(OverviewFiles); - _mediaType->hideStart(); + _mediaType->hideAnimated(); } void MainWidget::onAudiosSelect() { if (_overview) _overview->switchType(OverviewVoiceFiles); - _mediaType->hideStart(); + _mediaType->hideAnimated(); } void MainWidget::onLinksSelect() { if (_overview) _overview->switchType(OverviewLinks); - _mediaType->hideStart(); + _mediaType->hideAnimated(); } Window::TopBarWidget *MainWidget::topBar() { diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 69c8b4ddd..60f7b6f6c 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -39,6 +39,7 @@ class Panel; namespace Ui { class PeerAvatarButton; class PlainShadow; +class DropdownMenu; } // namespace Ui namespace Window { @@ -56,7 +57,6 @@ class DialogsWidget; class HistoryWidget; class OverviewWidget; class HistoryHider; -class Dropdown; enum StackItemType { HistoryStackItem, @@ -609,7 +609,7 @@ private: int _playerHeight = 0; int _contentScrollAddToY = 0; - ChildWidget _mediaType; + ChildWidget _mediaType; int32 _mediaTypeMask = 0; int32 updDate = 0; diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp index 54f4ace9c..2472c2b32 100644 --- a/Telegram/SourceFiles/mainwindow.cpp +++ b/Telegram/SourceFiles/mainwindow.cpp @@ -23,7 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "dialogs/dialogs_layout.h" #include "styles/style_dialogs.h" -#include "ui/popupmenu.h" +#include "ui/widgets/popup_menu.h" #include "zip.h" #include "lang.h" #include "shortcuts.h" @@ -185,7 +185,7 @@ void MainWindow::onWindowActiveChanged() { void MainWindow::firstShow() { #ifdef Q_OS_WIN - trayIconMenu = new PopupMenu(); + trayIconMenu = new Ui::PopupMenu(); trayIconMenu->deleteOnHide(false); #else // Q_OS_WIN trayIconMenu = new QMenu(this); diff --git a/Telegram/SourceFiles/media/player/media_player_list.h b/Telegram/SourceFiles/media/player/media_player_list.h index bead9a398..b6aa5f1da 100644 --- a/Telegram/SourceFiles/media/player/media_player_list.h +++ b/Telegram/SourceFiles/media/player/media_player_list.h @@ -29,7 +29,7 @@ class Document; namespace Media { namespace Player { -class ListWidget : public ScrolledWidget, private base::Subscriber { +class ListWidget : public TWidget, private base::Subscriber { public: ListWidget(); diff --git a/Telegram/SourceFiles/media/player/media_player_panel.cpp b/Telegram/SourceFiles/media/player/media_player_panel.cpp index 19bd4fc44..2ec37694d 100644 --- a/Telegram/SourceFiles/media/player/media_player_panel.cpp +++ b/Telegram/SourceFiles/media/player/media_player_panel.cpp @@ -25,6 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "media/player/media_player_list.h" #include "media/player/media_player_instance.h" #include "styles/style_overview.h" +#include "styles/style_widgets.h" #include "styles/style_media_player.h" #include "ui/widgets/shadow.h" #include "mainwindow.h" @@ -34,7 +35,7 @@ namespace Player { Panel::Panel(QWidget *parent, Layout layout) : TWidget(parent) , _layout(layout) -, _shadow(st::defaultInnerDropdown.shadow) +, _shadow(st::defaultDropdownShadow) , _scroll(this, st::mediaPlayerScroll) { _hideTimer.setSingleShot(true); connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(onHideStart())); @@ -90,7 +91,7 @@ void Panel::updateControlsGeometry() { if (scrollHeight > 0) { _scroll->setGeometryToRight(contentRight(), scrollTop, width, scrollHeight); } - if (auto widget = static_cast(_scroll->widget())) { + if (auto widget = static_cast(_scroll->widget())) { widget->resizeToWidth(width); onScroll(); } @@ -118,7 +119,7 @@ void Panel::scrollPlaylistToCurrentTrack() { } void Panel::onScroll() { - if (auto widget = static_cast(_scroll->widget())) { + if (auto widget = static_cast(_scroll->widget())) { int visibleTop = _scroll->scrollTop(); int visibleBottom = visibleTop + _scroll->height(); widget->setVisibleTopBottom(visibleTop, visibleBottom); @@ -153,7 +154,7 @@ void Panel::paintEvent(QPaintEvent *e) { if (animating) { p.setOpacity(_a_appearance.current(_hiding ? 0. : 1.)); } else if (_hiding || isHidden()) { - hidingFinished(); + hideFinished(); return; } p.drawPixmap(0, 0, _cache); @@ -290,7 +291,7 @@ void Panel::onShowStart() { void Panel::hideIgnoringEnterEvents() { _ignoringEnterEvents = true; if (isHidden()) { - hidingFinished(); + hideFinished(); } else { onHideStart(); } @@ -317,13 +318,13 @@ void Panel::startAnimation() { void Panel::appearanceCallback() { if (!_a_appearance.animating() && _hiding) { _hiding = false; - hidingFinished(); + hideFinished(); } else { update(); } } -void Panel::hidingFinished() { +void Panel::hideFinished() { hide(); _cache = QPixmap(); performDestroy(); diff --git a/Telegram/SourceFiles/media/player/media_player_panel.h b/Telegram/SourceFiles/media/player/media_player_panel.h index aa87c5981..03b95c52a 100644 --- a/Telegram/SourceFiles/media/player/media_player_panel.h +++ b/Telegram/SourceFiles/media/player/media_player_panel.h @@ -81,7 +81,7 @@ private: void updateSize(); void appearanceCallback(); - void hidingFinished(); + void hideFinished(); int contentLeft() const; int contentTop() const; int contentRight() const; diff --git a/Telegram/SourceFiles/media/player/media_player_volume_controller.h b/Telegram/SourceFiles/media/player/media_player_volume_controller.h index 980028ca2..305654995 100644 --- a/Telegram/SourceFiles/media/player/media_player_volume_controller.h +++ b/Telegram/SourceFiles/media/player/media_player_volume_controller.h @@ -56,9 +56,6 @@ public: bool overlaps(const QRect &globalRect); - void otherEnter(); - void otherLeave(); - QMargins getMargin() const; protected: @@ -75,6 +72,9 @@ private slots: void onWindowActiveChanged(); private: + void otherEnter(); + void otherLeave(); + void appearanceCallback(); void hidingFinished(); void startAnimation(); diff --git a/Telegram/SourceFiles/media/view/mediaview.style b/Telegram/SourceFiles/media/view/mediaview.style index 7f43918ef..7171d2b12 100644 --- a/Telegram/SourceFiles/media/view/mediaview.style +++ b/Telegram/SourceFiles/media/view/mediaview.style @@ -110,3 +110,50 @@ mediaviewFileBlue: icon { mediaviewTransparentBg: #ffffff; mediaviewTransparentFg: #cccccc; mediaviewTransparentSize: 4px; + +mediaviewMenu: Menu(defaultMenu) { + itemBg: #383838; + itemBgOver: #505050; + itemFg: white; + itemFgOver: white; + itemFgDisabled: #999; + itemFgShortcut: #eee; + itemFgShortcutOver: #fff; + itemFgShortcutDisabled: #999; + + separatorFg: #484848; +} +mediaviewPopupMenu: PopupMenu(defaultPopupMenu) { + shadow: icon {}; + menu: mediaviewMenu; +} +mediaviewDropdownMenu: DropdownMenu(defaultDropdownMenu) { + menu: mediaviewMenu; +} +/* +mvDropdown: dropdown(dropdownDef) { + shadow: icon {}; + padding: margins(11px, 12px, 11px, 12px); + + border: 0px; + width: 182px; +} +mvButton: iconedButton(btnDefIconed) { + bgColor: #383838; + overBgColor: #505050; + font: font(fsize); + + opacity: 1.; + overOpacity: 1.; + + width: -32px; + height: 36px; + + color: white; + + textPos: point(16px, 9px); + downTextPos: point(16px, 10px); + + duration: 0; +} +*/ \ No newline at end of file diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index eb3c30909..6b497473f 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -26,7 +26,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwindow.h" #include "application.h" #include "ui/filedialog.h" -#include "ui/popupmenu.h" +#include "ui/widgets/popup_menu.h" #include "media/media_clip_reader.h" #include "media/view/media_clip_controller.h" #include "styles/style_mediaview.h" @@ -88,7 +88,7 @@ MediaView::MediaView() : TWidget(App::wnd()) , _radial(animation(this, &MediaView::step_radial)) , _lastAction(-st::mvDeltaFromLastAction, -st::mvDeltaFromLastAction) , _a_state(animation(this, &MediaView::step_state)) -, _dropdown(this, st::mvDropdown) { +, _dropdown(this, st::mediaviewDropdownMenu) { TextCustomTagsMap custom; custom.insert(QChar('c'), qMakePair(textcmdStartLink(1), textcmdStopLink())); _saveMsgText.setRichText(st::medviewSaveMsgFont, lang(lng_mediaview_saved), _textDlgOptions, custom); @@ -126,32 +126,14 @@ MediaView::MediaView() : TWidget(App::wnd()) _touchTimer.setSingleShot(true); connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer())); - _btns.push_back(_btnSaveCancel = _dropdown.addButton(new IconedButton(this, st::mvButton, lang(lng_cancel)))); - connect(_btnSaveCancel, SIGNAL(clicked()), this, SLOT(onSaveCancel())); - _btns.push_back(_btnToMessage = _dropdown.addButton(new IconedButton(this, st::mvButton, lang(lng_context_to_msg)))); - connect(_btnToMessage, SIGNAL(clicked()), this, SLOT(onToMessage())); - _btns.push_back(_btnShowInFolder = _dropdown.addButton(new IconedButton(this, st::mvButton, lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_context_show_in_finder : lng_context_show_in_folder)))); - connect(_btnShowInFolder, SIGNAL(clicked()), this, SLOT(onShowInFolder())); - _btns.push_back(_btnCopy = _dropdown.addButton(new IconedButton(this, st::mvButton, lang(lng_mediaview_copy)))); - connect(_btnCopy, SIGNAL(clicked()), this, SLOT(onCopy())); - _btns.push_back(_btnForward = _dropdown.addButton(new IconedButton(this, st::mvButton, lang(lng_mediaview_forward)))); - connect(_btnForward, SIGNAL(clicked()), this, SLOT(onForward())); - _btns.push_back(_btnDelete = _dropdown.addButton(new IconedButton(this, st::mvButton, lang(lng_mediaview_delete)))); - connect(_btnDelete, SIGNAL(clicked()), this, SLOT(onDelete())); - _btns.push_back(_btnSaveAs = _dropdown.addButton(new IconedButton(this, st::mvButton, lang(lng_mediaview_save_as)))); - connect(_btnSaveAs, SIGNAL(clicked()), this, SLOT(onSaveAs())); - _btns.push_back(_btnViewAll = _dropdown.addButton(new IconedButton(this, st::mvButton, lang(lng_mediaview_photos_all)))); - connect(_btnViewAll, SIGNAL(clicked()), this, SLOT(onOverview())); - - _dropdown.hide(); - connect(&_dropdown, SIGNAL(hiding()), this, SLOT(onDropdownHiding())); - _controlsHideTimer.setSingleShot(true); connect(&_controlsHideTimer, SIGNAL(timeout()), this, SLOT(onHideControls())); connect(&_docDownload, SIGNAL(clicked()), this, SLOT(onDownload())); connect(&_docSaveAs, SIGNAL(clicked()), this, SLOT(onSaveAs())); connect(&_docCancel, SIGNAL(clicked()), this, SLOT(onSaveCancel())); + + connect(_dropdown, SIGNAL(beforeHidden()), this, SLOT(onDropdownHidden())); } void MediaView::moveToScreen() { @@ -400,18 +382,31 @@ void MediaView::updateControls() { update(); } -void MediaView::updateDropdown() { - _btnSaveCancel->setVisible(_doc && _doc->loading()); - _btnToMessage->setVisible(_msgid > 0); - _btnShowInFolder->setVisible(_doc && !_doc->filepath(DocumentData::FilePathResolveChecked).isEmpty()); - _btnSaveAs->setVisible(true); - _btnCopy->setVisible((_doc && fileShown()) || (_photo && _photo->loaded())); - _btnForward->setVisible(_canForward); - _btnDelete->setVisible(_canDelete || (_photo && App::self() && _user == App::self()) || (_photo && _photo->peer && _photo->peer->photoId == _photo->id && (_photo->peer->isChat() || (_photo->peer->isChannel() && _photo->peer->asChannel()->amCreator())))); - _btnViewAll->setVisible(_history && typeHasMediaOverview(_overview)); - _btnViewAll->setText(lang(_doc ? lng_mediaview_files_all : lng_mediaview_photos_all)); - _dropdown.updateButtons(); - _dropdown.moveToRight(0, height() - _dropdown.height()); +void MediaView::updateActions() { + _actions.clear(); + + if (_doc && _doc->loading()) { + _actions.push_back({ lang(lng_cancel), SLOT(onSaveCancel()) }); + } + if (_msgid > 0) { + _actions.push_back({ lang(lng_context_to_msg), SLOT(onToMessage()) }); + } + if (_doc && !_doc->filepath(DocumentData::FilePathResolveChecked).isEmpty()) { + _actions.push_back({ lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_context_show_in_finder : lng_context_show_in_folder), SLOT(onShowInFolder()) }); + } + if ((_doc && fileShown()) || (_photo && _photo->loaded())) { + _actions.push_back({ lang(lng_mediaview_copy), SLOT(onCopy()) }); + } + if (_canForward) { + _actions.push_back({ lang(lng_mediaview_forward), SLOT(onForward()) }); + } + if (_canDelete || (_photo && App::self() && _user == App::self()) || (_photo && _photo->peer && _photo->peer->photoId == _photo->id && (_photo->peer->isChat() || (_photo->peer->isChannel() && _photo->peer->asChannel()->amCreator())))) { + _actions.push_back({ lang(lng_mediaview_delete), SLOT(onDelete()) }); + } + _actions.push_back({ lang(lng_mediaview_save_as), SLOT(onSaveAs()) }); + if (_history && typeHasMediaOverview(_overview)) { + _actions.push_back({ lang(_doc ? lng_mediaview_files_all : lng_mediaview_photos_all), SLOT(onOverview()) }); + } } void MediaView::step_state(uint64 ms, bool timer) { @@ -662,7 +657,7 @@ void MediaView::activateControls() { void MediaView::onHideControls(bool force) { if (!force) { - if (!_dropdown.isHidden() + if (!_dropdown->isHidden() || _menu || _mousePressed || (_fullScreenVideo && _clipController && _clipController->geometry().contains(_lastMouseMovePos))) { @@ -682,7 +677,7 @@ void MediaView::onHideControls(bool force) { if (!_a_state.animating()) _a_state.start(); } -void MediaView::onDropdownHiding() { +void MediaView::onDropdownHidden() { setFocus(); _ignoringDropdown = true; _lastMouseMovePos = mapFromGlobal(QCursor::pos()); @@ -961,10 +956,7 @@ void MediaView::onOverview() { } void MediaView::onCopy() { - if (!_dropdown.isHidden()) { - _dropdown.ignoreShow(); - _dropdown.hideStart(); - } + _dropdown->hideAnimated(Ui::DropdownMenu::HideOption::IgnoreShow); if (_doc) { if (!_current.isNull()) { QApplication::clipboard()->setPixmap(_current); @@ -2395,10 +2387,10 @@ void MediaView::contextMenuEvent(QContextMenuEvent *e) { _menu->deleteLater(); _menu = 0; } - _menu = new PopupMenu(st::mvPopupMenu); - updateDropdown(); - for (int32 i = 0, l = _btns.size(); i < l; ++i) { - if (!_btns.at(i)->isHidden()) _menu->addAction(_btns.at(i)->getText(), _btns.at(i), SIGNAL(clicked()))->setEnabled(true); + _menu = new Ui::PopupMenu(st::mediaviewPopupMenu); + updateActions(); + for_const (auto &action, _actions) { + _menu->addAction(action.text, this, action.member)->setEnabled(true); } connect(_menu, SIGNAL(destroyed(QObject*)), this, SLOT(onMenuDestroy(QObject*))); _menu->popup(e->globalPos()); @@ -2541,10 +2533,14 @@ void MediaView::receiveMouse() { } void MediaView::onDropdown() { - updateDropdown(); - _dropdown.ignoreShow(false); - _dropdown.showStart(); - _dropdown.setFocus(); + updateActions(); + _dropdown->clearActions(); + for_const (auto &action, _actions) { + _dropdown->addAction(action.text, this, action.member); + } + _dropdown->moveToRight(0, height() - _dropdown->height()); + _dropdown->showAnimated(); + _dropdown->setFocus(); } void MediaView::onCheckActive() { diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h index b86383165..2f4600851 100644 --- a/Telegram/SourceFiles/mediaview.h +++ b/Telegram/SourceFiles/mediaview.h @@ -20,7 +20,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "dropdown.h" +#include "ui/widgets/dropdown_menu.h" #include "ui/effects/radial_animation.h" namespace Media { @@ -29,7 +29,9 @@ class Controller; } // namespace Clip } // namespace Media +namespace Ui { class PopupMenu; +} // namespace Ui struct AudioPlaybackState; @@ -60,9 +62,6 @@ public: void mediaOverviewUpdated(PeerData *peer, MediaOverviewType type); void documentUpdated(DocumentData *doc); void changingMsgId(HistoryItem *row, MsgId newId); - void updateDocSize(); - void updateControls(); - void updateDropdown(); void showSaveMsgFile(); void close(); @@ -81,9 +80,9 @@ public: void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; -public slots: +private slots: void onHideControls(bool force = false); - void onDropdownHiding(); + void onDropdownHidden(); void onScreenResized(int screen); @@ -130,6 +129,10 @@ private slots: void onVideoPlayProgress(const AudioMsgId &audioId); private: + void updateDocSize(); + void updateControls(); + void updateActions(); + void displayPhoto(PhotoData *photo, HistoryItem *item); void displayDocument(DocumentData *doc, HistoryItem *item); void displayFinished(); @@ -304,10 +307,14 @@ private: anim::fvalue a_cOpacity; bool _mousePressed = false; - PopupMenu *_menu = nullptr; - Dropdown _dropdown; - IconedButton *_btnSaveCancel, *_btnToMessage, *_btnShowInFolder, *_btnSaveAs, *_btnCopy, *_btnForward, *_btnDelete, *_btnViewAll; - QList _btns; + Ui::PopupMenu *_menu = nullptr; + ChildWidget _dropdown; + + struct ActionData { + QString text; + const char *member; + }; + QList _actions; bool _receiveMouse = true; diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index c1a2ba9b6..e75fd1d1d 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -25,6 +25,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "boxes/confirmbox.h" #include "boxes/photocropbox.h" #include "ui/filedialog.h" +#include "ui/widgets/popup_menu.h" +#include "ui/widgets/tooltip.h" #include "window/top_bar_widget.h" #include "window/chat_background.h" #include "lang.h" @@ -931,22 +933,22 @@ void OverviewInner::onUpdateSelected() { Qt::CursorShape cur = style::cur_default; bool lnkChanged = ClickHandler::setActive(lnk, lnkhost); if (lnkChanged) { - PopupTooltip::Hide(); + Ui::Tooltip::Hide(); } App::mousedItem(item); if (_mousedItem != oldMousedItem) { - PopupTooltip::Hide(); + Ui::Tooltip::Hide(); if (oldMousedItem) repaintItem(oldMousedItem, oldMousedItemIndex); if (item) repaintItem(item); } if (_cursorState == HistoryInDateCursorState && cursorState != HistoryInDateCursorState) { - PopupTooltip::Hide(); + Ui::Tooltip::Hide(); } if (cursorState != _cursorState) { _cursorState = cursorState; } if (lnk || cursorState == HistoryInDateCursorState) { - PopupTooltip::Show(1000, this); + Ui::Tooltip::Show(1000, this); } fixItemIndex(_dragItemIndex, _dragItem); @@ -1183,7 +1185,7 @@ void OverviewInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { bool lnkIsAudio = lnkDocument ? (lnkDocument->document()->voice() != nullptr) : false; bool lnkIsSong = lnkDocument ? (lnkDocument->document()->song() != nullptr) : false; if (lnkPhoto || lnkDocument) { - _menu = new PopupMenu(); + _menu = new Ui::PopupMenu(); if (App::hoveredLinkItem()) { _menu->addAction(lang(lng_context_to_msg), this, SLOT(goToMessage()))->setEnabled(true); } @@ -1221,7 +1223,7 @@ void OverviewInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { repaintItem(App::contextItem()); if (_selectedMsgId) repaintItem(_selectedMsgId, -1); } else if (!ignoreMousedItem && App::mousedItem() && App::mousedItem()->channelId() == itemChannel(_mousedItem) && App::mousedItem()->id == itemMsgId(_mousedItem)) { - _menu = new PopupMenu(); + _menu = new Ui::PopupMenu(); QString linkCopyToClipboardText = _contextMenuLnk ? _contextMenuLnk->copyToClipboardContextItemText() : QString(); if (!linkCopyToClipboardText.isEmpty()) { _menu->addAction(linkCopyToClipboardText, this, SLOT(copyContextUrl()))->setEnabled(true); diff --git a/Telegram/SourceFiles/overviewwidget.h b/Telegram/SourceFiles/overviewwidget.h index f43728545..abd092a8d 100644 --- a/Telegram/SourceFiles/overviewwidget.h +++ b/Telegram/SourceFiles/overviewwidget.h @@ -21,7 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once #include "window/section_widget.h" -#include "ui/popupmenu.h" +#include "ui/widgets/tooltip.h" namespace Overview { namespace Layout { @@ -33,10 +33,11 @@ class Date; namespace Ui { class PlainShadow; +class PopupMenu; } // namespace Ui class OverviewWidget; -class OverviewInner : public TWidget, public AbstractTooltipShower, public RPCSender, private base::Subscriber { +class OverviewInner : public TWidget, public Ui::AbstractTooltipShower, public RPCSender, private base::Subscriber { Q_OBJECT public: @@ -263,7 +264,7 @@ private: uint64 _touchTime = 0; QTimer _touchScrollTimer; - PopupMenu *_menu = nullptr; + Ui::PopupMenu *_menu = nullptr; }; class OverviewWidget : public TWidget, public RPCSender { diff --git a/Telegram/SourceFiles/platform/win/main_window_win.cpp b/Telegram/SourceFiles/platform/win/main_window_win.cpp index fe5365f38..dfb2b1198 100644 --- a/Telegram/SourceFiles/platform/win/main_window_win.cpp +++ b/Telegram/SourceFiles/platform/win/main_window_win.cpp @@ -28,7 +28,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "application.h" #include "lang.h" #include "localstorage.h" -#include "ui/popupmenu.h" +#include "ui/widgets/popup_menu.h" #include diff --git a/Telegram/SourceFiles/platform/win/main_window_win.h b/Telegram/SourceFiles/platform/win/main_window_win.h index 20713abe4..aca3eda28 100644 --- a/Telegram/SourceFiles/platform/win/main_window_win.h +++ b/Telegram/SourceFiles/platform/win/main_window_win.h @@ -23,7 +23,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "window/main_window.h" #include +namespace Ui { class PopupMenu; +} // namespace Ui namespace Platform { @@ -109,7 +111,7 @@ protected: bool posInited = false; QSystemTrayIcon *trayIcon = nullptr; - PopupMenu *trayIconMenu = nullptr; + Ui::PopupMenu *trayIconMenu = nullptr; QImage icon256, iconbig256; QIcon wndIcon; diff --git a/Telegram/SourceFiles/profile/profile_block_widget.cpp b/Telegram/SourceFiles/profile/profile_block_widget.cpp index 93007eefc..1b287cc6e 100644 --- a/Telegram/SourceFiles/profile/profile_block_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_block_widget.cpp @@ -25,7 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Profile { -BlockWidget::BlockWidget(QWidget *parent, PeerData *peer, const QString &title) : ScrolledWidget(parent) +BlockWidget::BlockWidget(QWidget *parent, PeerData *peer, const QString &title) : TWidget(parent) , _peer(peer) , _title(title) { } diff --git a/Telegram/SourceFiles/profile/profile_block_widget.h b/Telegram/SourceFiles/profile/profile_block_widget.h index d185f9f04..a580803df 100644 --- a/Telegram/SourceFiles/profile/profile_block_widget.h +++ b/Telegram/SourceFiles/profile/profile_block_widget.h @@ -24,7 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Profile { -class BlockWidget : public ScrolledWidget, protected base::Subscriber { +class BlockWidget : public TWidget, protected base::Subscriber { Q_OBJECT public: diff --git a/Telegram/SourceFiles/settings/settings_block_widget.cpp b/Telegram/SourceFiles/settings/settings_block_widget.cpp index 2d143ac84..8a188de02 100644 --- a/Telegram/SourceFiles/settings/settings_block_widget.cpp +++ b/Telegram/SourceFiles/settings/settings_block_widget.cpp @@ -26,7 +26,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Settings { -BlockWidget::BlockWidget(QWidget *parent, UserData *self, const QString &title) : ScrolledWidget(parent) +BlockWidget::BlockWidget(QWidget *parent, UserData *self, const QString &title) : TWidget(parent) , _self(self) , _title(title) { } diff --git a/Telegram/SourceFiles/settings/settings_block_widget.h b/Telegram/SourceFiles/settings/settings_block_widget.h index 6f34ae152..059973d04 100644 --- a/Telegram/SourceFiles/settings/settings_block_widget.h +++ b/Telegram/SourceFiles/settings/settings_block_widget.h @@ -32,7 +32,7 @@ class WidgetSlideWrap; namespace Settings { -class BlockWidget : public ScrolledWidget, protected base::Subscriber { +class BlockWidget : public TWidget, protected base::Subscriber { Q_OBJECT public: diff --git a/Telegram/SourceFiles/stickers/emoji_pan.cpp b/Telegram/SourceFiles/stickers/emoji_pan.cpp index 74dca4181..fc0dd4799 100644 --- a/Telegram/SourceFiles/stickers/emoji_pan.cpp +++ b/Telegram/SourceFiles/stickers/emoji_pan.cpp @@ -42,19 +42,19 @@ EmojiColorPicker::EmojiColorPicker() : TWidget() , _a_selected(animation(this, &EmojiColorPicker::step_selected)) , a_opacity(0) , _a_appearance(animation(this, &EmojiColorPicker::step_appearance)) -, _shadow(st::dropdownDef.shadow) { +, _shadow(st::defaultDropdownShadow) { memset(_variants, 0, sizeof(_variants)); memset(_hovers, 0, sizeof(_hovers)); setMouseTracking(true); setFocusPolicy(Qt::NoFocus); - int32 w = st::emojiPanSize.width() * (EmojiColorsCount + 1) + 4 * st::emojiColorsPadding + st::emojiColorsSep + st::dropdownDef.shadow.width() * 2; - int32 h = 2 * st::emojiColorsPadding + st::emojiPanSize.height() + st::dropdownDef.shadow.height() * 2; + int32 w = st::emojiPanSize.width() * (EmojiColorsCount + 1) + 4 * st::emojiColorsPadding + st::emojiColorsSep + st::defaultDropdownShadow.width() * 2; + int32 h = 2 * st::emojiColorsPadding + st::emojiPanSize.height() + st::defaultDropdownShadow.height() * 2; resize(w, h); _hideTimer.setSingleShot(true); - connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideStart())); + connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideAnimated())); } void EmojiColorPicker::showEmoji(uint32 code) { @@ -72,7 +72,7 @@ void EmojiColorPicker::showEmoji(uint32 code) { _variants[5] = emojiGet(e, 0xD83CDFFF); if (!_cache.isNull()) _cache = QPixmap(); - showStart(); + showAnimated(); } void EmojiColorPicker::paintEvent(QPaintEvent *e) { @@ -85,12 +85,12 @@ void EmojiColorPicker::paintEvent(QPaintEvent *e) { p.setClipRect(e->rect()); } - int32 w = st::dropdownDef.shadow.width(), h = st::dropdownDef.shadow.height(); + int32 w = st::defaultDropdownShadow.width(), h = st::defaultDropdownShadow.height(); QRect r = QRect(w, h, width() - 2 * w, height() - 2 * h); - _shadow.paint(p, r, st::dropdownDef.shadowShift); + _shadow.paint(p, r, st::defaultDropdownShadowShift); if (_cache.isNull()) { - p.fillRect(e->rect().intersected(r), st::white->b); + p.fillRect(e->rect().intersected(r), st::white); int32 x = w + 2 * st::emojiColorsPadding + st::emojiPanSize.width(); if (rtl()) x = width() - x - st::emojiColorsSep; @@ -108,7 +108,7 @@ void EmojiColorPicker::paintEvent(QPaintEvent *e) { void EmojiColorPicker::enterEvent(QEvent *e) { _hideTimer.stop(); - if (_hiding) showStart(); + if (_hiding) showAnimated(); TWidget::enterEvent(e); } @@ -135,7 +135,7 @@ void EmojiColorPicker::mouseReleaseEvent(QMouseEvent *e) { emit emojiSelected(_variants[_selected]); } _ignoreShow = true; - hideStart(); + hideAnimated(); } void EmojiColorPicker::mouseMoveEvent(QMouseEvent *e) { @@ -148,7 +148,7 @@ void EmojiColorPicker::step_appearance(float64 ms, bool timer) { _a_appearance.stop(); return; } - float64 dt = ms / st::dropdownDef.duration; + float64 dt = ms / st::defaultDropdownDuration; if (dt >= 1) { a_opacity.finish(); _cache = QPixmap(); @@ -178,34 +178,34 @@ void EmojiColorPicker::step_selected(uint64 ms, bool timer) { _hovers[index] = (i.key() > 0) ? dt : (1 - dt); ++i; } - toUpdate += QRect(st::dropdownDef.shadow.width() + st::emojiColorsPadding + index * st::emojiPanSize.width() + (index ? 2 * st::emojiColorsPadding + st::emojiColorsSep : 0), st::dropdownDef.shadow.height() + st::emojiColorsPadding, st::emojiPanSize.width(), st::emojiPanSize.height()); + toUpdate += QRect(st::defaultDropdownShadow.width() + st::emojiColorsPadding + index * st::emojiPanSize.width() + (index ? 2 * st::emojiColorsPadding + st::emojiColorsSep : 0), st::defaultDropdownShadow.height() + st::emojiColorsPadding, st::emojiPanSize.width(), st::emojiPanSize.height()); } if (timer) rtlupdate(toUpdate.boundingRect()); if (_emojiAnimations.isEmpty()) _a_selected.stop(); } -void EmojiColorPicker::hideStart(bool fast) { - if (fast) { - clearSelection(true); - if (_a_appearance.animating()) _a_appearance.stop(); - if (_a_selected.animating()) _a_selected.stop(); - a_opacity = anim::fvalue(0); - _cache = QPixmap(); - hide(); - emit hidden(); - } else { - if (_cache.isNull()) { - int32 w = st::dropdownDef.shadow.width(), h = st::dropdownDef.shadow.height(); - _cache = myGrab(this, QRect(w, h, width() - 2 * w, height() - 2 * h)); - clearSelection(true); - } - _hiding = true; - a_opacity.start(0); - _a_appearance.start(); - } +void EmojiColorPicker::hideFast() { + clearSelection(true); + if (_a_appearance.animating()) _a_appearance.stop(); + if (_a_selected.animating()) _a_selected.stop(); + a_opacity = anim::fvalue(0); + _cache = QPixmap(); + hide(); + emit hidden(); } -void EmojiColorPicker::showStart() { +void EmojiColorPicker::hideAnimated() { + if (_cache.isNull()) { + int32 w = st::defaultDropdownShadow.width(), h = st::defaultDropdownShadow.height(); + _cache = myGrab(this, QRect(w, h, width() - 2 * w, height() - 2 * h)); + clearSelection(true); + } + _hiding = true; + a_opacity.start(0); + _a_appearance.start(); +} + +void EmojiColorPicker::showAnimated() { if (_ignoreShow) return; _hiding = false; @@ -217,7 +217,7 @@ void EmojiColorPicker::showStart() { return; } if (_cache.isNull()) { - int32 w = st::dropdownDef.shadow.width(), h = st::dropdownDef.shadow.height(); + int32 w = st::defaultDropdownShadow.width(), h = st::defaultDropdownShadow.height(); _cache = myGrab(this, QRect(w, h, width() - 2 * w, height() - 2 * h)); clearSelection(true); } @@ -241,9 +241,9 @@ void EmojiColorPicker::clearSelection(bool fast) { void EmojiColorPicker::updateSelected() { int32 selIndex = -1; QPoint p(mapFromGlobal(_lastMousePos)); - int32 sx = rtl() ? (width() - p.x()) : p.x(), y = p.y() - st::dropdownDef.shadow.height() - st::emojiColorsPadding; + int32 sx = rtl() ? (width() - p.x()) : p.x(), y = p.y() - st::defaultDropdownShadow.height() - st::emojiColorsPadding; if (y >= 0 && y < st::emojiPanSize.height()) { - int32 x = sx - st::dropdownDef.shadow.width() - st::emojiColorsPadding; + int32 x = sx - st::defaultDropdownShadow.width() - st::emojiColorsPadding; if (x >= 0 && x < st::emojiPanSize.width()) { selIndex = 0; } else { @@ -279,7 +279,7 @@ void EmojiColorPicker::updateSelected() { void EmojiColorPicker::drawVariant(Painter &p, int variant) { float64 hover = _hovers[variant]; - QPoint w(st::dropdownDef.shadow.width() + st::emojiColorsPadding + variant * st::emojiPanSize.width() + (variant ? 2 * st::emojiColorsPadding + st::emojiColorsSep : 0), st::dropdownDef.shadow.height() + st::emojiColorsPadding); + QPoint w(st::defaultDropdownShadow.width() + st::emojiColorsPadding + variant * st::emojiPanSize.width() + (variant ? 2 * st::emojiColorsPadding + st::emojiColorsSep : 0), st::defaultDropdownShadow.height() + st::emojiColorsPadding); if (hover > 0) { p.setOpacity(hover); QPoint tl(w); @@ -291,7 +291,7 @@ void EmojiColorPicker::drawVariant(Painter &p, int variant) { p.drawPixmapLeft(w.x() + (st::emojiPanSize.width() - (esize / cIntRetinaFactor())) / 2, w.y() + (st::emojiPanSize.height() - (esize / cIntRetinaFactor())) / 2, width(), App::emojiLarge(), QRect(_variants[variant]->x * esize, _variants[variant]->y * esize, esize, esize)); } -EmojiPanInner::EmojiPanInner() : ScrolledWidget() +EmojiPanInner::EmojiPanInner() : TWidget() , _maxHeight(int(st::emojiPanMaxHeight) - st::rbEmoji.height) , _a_selected(animation(this, &EmojiPanInner::step_selected)) { resize(st::emojiPanWidth - st::emojiScroll.width, countHeight()); @@ -404,7 +404,7 @@ void EmojiPanInner::paintEvent(QPaintEvent *e) { bool EmojiPanInner::checkPickerHide() { if (!_picker.isHidden() && _selected == _pickerSel) { - _picker.hideStart(); + _picker.hideAnimated(); _pickerSel = -1; updateSelected(); return true; @@ -446,7 +446,7 @@ void EmojiPanInner::mouseReleaseEvent(QMouseEvent *e) { int tab = (_pickerSel / MatrixRowShift), sel = _pickerSel % MatrixRowShift; if (tab < emojiTabCount && sel < _emojis[tab].size() && _emojis[tab][sel]->color) { if (cEmojiVariants().constFind(_emojis[tab][sel]->code) != cEmojiVariants().cend()) { - _picker.hideStart(); + _picker.hideAnimated(); _pickerSel = -1; } } @@ -576,7 +576,7 @@ void EmojiPanInner::onColorSelected(EmojiPtr emoji) { } } selectEmoji(emoji); - _picker.hideStart(); + _picker.hideAnimated(); } void EmojiPanInner::mouseMoveEvent(QMouseEvent *e) { @@ -642,7 +642,7 @@ DBIEmojiTab EmojiPanInner::currentTab(int yOffset) const { void EmojiPanInner::hideFinish() { if (!_picker.isHidden()) { - _picker.hideStart(true); + _picker.hideFast(); _pickerSel = -1; clearSelection(true); } @@ -738,9 +738,9 @@ void EmojiPanInner::updateSelected() { setCursor((newSel >= 0) ? style::cur_pointer : style::cur_default); if (newSel >= 0 && !_picker.isHidden()) { if (newSel != _pickerSel) { - _picker.hideStart(); + _picker.hideAnimated(); } else { - _picker.showStart(); + _picker.showAnimated(); } } } @@ -793,7 +793,7 @@ void EmojiPanInner::showEmojiPack(DBIEmojiTab packIndex) { update(); } -StickerPanInner::StickerPanInner() : ScrolledWidget() +StickerPanInner::StickerPanInner() : TWidget() , _a_selected(animation(this, &StickerPanInner::step_selected)) , _section(cShowingSavedGifs() ? Section::Gifs : Section::Stickers) , _addText(lang(lng_stickers_featured_add).toUpper()) @@ -2550,7 +2550,7 @@ EmojiPan::EmojiPan(QWidget *parent) : TWidget(parent) , _contentHeightEmoji(_contentHeight - st::rbEmoji.height) , _contentHeightStickers(_contentHeight - st::rbEmoji.height) , _a_appearance(animation(this, &EmojiPan::step_appearance)) -, _shadow(st::dropdownDef.shadow) +, _shadow(st::defaultDropdownShadow) , _recent(this , qsl("emoji_group"), dbietRecent , QString(), true , st::rbEmojiRecent) , _people(this , qsl("emoji_group"), dbietPeople , QString(), false, st::rbEmojiPeople) , _nature(this , qsl("emoji_group"), dbietNature , QString(), false, st::rbEmojiNature) @@ -2573,24 +2573,24 @@ EmojiPan::EmojiPan(QWidget *parent) : TWidget(parent) s_scroll.setFocusPolicy(Qt::NoFocus); s_scroll.viewport()->setFocusPolicy(Qt::NoFocus); - _width = st::dropdownDef.padding.left() + st::emojiPanWidth + st::dropdownDef.padding.right(); - _height = st::dropdownDef.padding.top() + _contentHeight + st::dropdownDef.padding.bottom(); + _width = st::defaultDropdownPadding.left() + st::emojiPanWidth + st::defaultDropdownPadding.right(); + _height = st::defaultDropdownPadding.top() + _contentHeight + st::defaultDropdownPadding.bottom(); _bottom = 0; resize(_width, _height); e_scroll.resize(st::emojiPanWidth, _contentHeightEmoji); s_scroll.resize(st::emojiPanWidth, _contentHeightStickers); - e_scroll.move(st::dropdownDef.padding.left(), st::dropdownDef.padding.top()); + e_scroll.move(st::defaultDropdownPadding.left(), st::defaultDropdownPadding.top()); e_scroll.setWidget(&e_inner); - s_scroll.move(st::dropdownDef.padding.left(), st::dropdownDef.padding.top()); + s_scroll.move(st::defaultDropdownPadding.left(), st::defaultDropdownPadding.top()); s_scroll.setWidget(&s_inner); e_inner.moveToLeft(0, 0, e_scroll.width()); s_inner.moveToLeft(0, 0, s_scroll.width()); - int32 left = _iconsLeft = st::dropdownDef.padding.left() + (st::emojiPanWidth - 8 * st::rbEmoji.width) / 2; - int32 top = _iconsTop = st::dropdownDef.padding.top() + _contentHeight - st::rbEmoji.height; + int32 left = _iconsLeft = st::defaultDropdownPadding.left() + (st::emojiPanWidth - 8 * st::rbEmoji.width) / 2; + int32 top = _iconsTop = st::defaultDropdownPadding.top() + _contentHeight - st::rbEmoji.height; prepareTab(left, top, _width, _recent); prepareTab(left, top, _width, _people); prepareTab(left, top, _width, _nature); @@ -2603,7 +2603,7 @@ EmojiPan::EmojiPan(QWidget *parent) : TWidget(parent) updatePanelsPositions(e_panels, 0); _hideTimer.setSingleShot(true); - connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideStart())); + connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideAnimated())); connect(&e_inner, SIGNAL(scrollToY(int)), &e_scroll, SLOT(scrollToY(int))); connect(&e_inner, SIGNAL(disableScroll(bool)), &e_scroll, SLOT(disableScroll(bool))); @@ -2666,7 +2666,7 @@ void EmojiPan::updateContentHeight() { _contentHeightEmoji = he; _contentHeightStickers = hs; - _height = st::dropdownDef.padding.top() + _contentHeight + st::dropdownDef.padding.bottom(); + _height = st::defaultDropdownPadding.top() + _contentHeight + st::defaultDropdownPadding.bottom(); resize(_width, _height); move(x(), _bottom - height()); @@ -2683,7 +2683,7 @@ void EmojiPan::updateContentHeight() { s_scroll.resize(st::emojiPanWidth, _contentHeightStickers); } - _iconsTop = st::dropdownDef.padding.top() + _contentHeight - st::rbEmoji.height; + _iconsTop = st::defaultDropdownPadding.top() + _contentHeight - st::rbEmoji.height; _recent.move(_recent.x(), _iconsTop); _people.move(_people.x(), _iconsTop); _nature.move(_nature.x(), _iconsTop); @@ -2742,9 +2742,9 @@ void EmojiPan::paintEvent(QPaintEvent *e) { p.setOpacity(o = a_opacity.current()); } - QRect r(st::dropdownDef.padding.left(), st::dropdownDef.padding.top(), _width - st::dropdownDef.padding.left() - st::dropdownDef.padding.right(), _height - st::dropdownDef.padding.top() - st::dropdownDef.padding.bottom()); + QRect r(st::defaultDropdownPadding.left(), st::defaultDropdownPadding.top(), _width - st::defaultDropdownPadding.left() - st::defaultDropdownPadding.right(), _height - st::defaultDropdownPadding.top() - st::defaultDropdownPadding.bottom()); - _shadow.paint(p, r, st::dropdownDef.shadowShift); + _shadow.paint(p, r, st::defaultDropdownShadowShift); if (_toCache.isNull()) { if (_cache.isNull()) { @@ -2871,7 +2871,7 @@ void EmojiPan::moveBottom(int32 bottom, bool force) { void EmojiPan::enterEvent(QEvent *e) { _hideTimer.stop(); - if (_hiding) showStart(); + if (_hiding) showAnimated(); } bool EmojiPan::preventAutoHide() const { @@ -2881,7 +2881,7 @@ bool EmojiPan::preventAutoHide() const { void EmojiPan::leaveEvent(QEvent *e) { if (preventAutoHide() || s_inner.inlineResultsShown()) return; if (_a_appearance.animating()) { - hideStart(); + hideAnimated(); } else { _hideTimer.start(300); } @@ -2889,13 +2889,13 @@ void EmojiPan::leaveEvent(QEvent *e) { void EmojiPan::otherEnter() { _hideTimer.stop(); - showStart(); + showAnimated(); } void EmojiPan::otherLeave() { if (preventAutoHide() || s_inner.inlineResultsShown()) return; if (_a_appearance.animating()) { - hideStart(); + hideAnimated(); } else { _hideTimer.start(0); } @@ -2990,7 +2990,7 @@ bool EmojiPan::event(QEvent *e) { return TWidget::event(e); } -void EmojiPan::fastHide() { +void EmojiPan::hideFast() { if (_a_appearance.animating()) { _a_appearance.stop(); } @@ -3107,7 +3107,7 @@ void EmojiPan::updateSelected() { void EmojiPan::updateIcons() { if (!_stickersShown || !s_inner.showSectionIcons()) return; - QRect r(st::dropdownDef.padding.left(), st::dropdownDef.padding.top(), _width - st::dropdownDef.padding.left() - st::dropdownDef.padding.right(), _height - st::dropdownDef.padding.top() - st::dropdownDef.padding.bottom()); + QRect r(st::defaultDropdownPadding.left(), st::defaultDropdownPadding.top(), _width - st::defaultDropdownPadding.left() - st::defaultDropdownPadding.right(), _height - st::defaultDropdownPadding.top() - st::defaultDropdownPadding.bottom()); update(r.left(), _iconsTop, r.width(), st::rbEmoji.height); } @@ -3177,7 +3177,7 @@ void EmojiPan::step_appearance(float64 ms, bool timer) { return; } - float64 dt = ms / st::dropdownDef.duration; + float64 dt = ms / st::defaultDropdownDuration; if (dt >= 1) { _a_appearance.stop(); a_opacity.finish(); @@ -3193,10 +3193,10 @@ void EmojiPan::step_appearance(float64 ms, bool timer) { if (timer) update(); } -void EmojiPan::hideStart() { - if (preventAutoHide() || s_inner.inlineResultsShown()) return; +void EmojiPan::hideAnimated() { + if (isHidden() || preventAutoHide() || s_inner.inlineResultsShown()) return; - hideAnimated(); + startHideAnimated(); } void EmojiPan::prepareShowHideCache() { @@ -3204,12 +3204,12 @@ void EmojiPan::prepareShowHideCache() { QPixmap from = _fromCache, to = _toCache; _fromCache = _toCache = QPixmap(); showAll(); - _cache = myGrab(this, rect().marginsRemoved(st::dropdownDef.padding)); + _cache = myGrab(this, rect().marginsRemoved(st::defaultDropdownPadding)); _fromCache = from; _toCache = to; } } -void EmojiPan::hideAnimated() { +void EmojiPan::startHideAnimated() { if (_hiding) return; prepareShowHideCache(); @@ -3247,7 +3247,7 @@ void EmojiPan::hideFinish() { Notify::clipStopperHidden(ClipStopperSavedGifsPanel); } -void EmojiPan::showStart() { +void EmojiPan::showAnimated() { if (!isHidden() && !_hiding) { return; } @@ -3297,7 +3297,7 @@ bool EmojiPan::eventFilter(QObject *obj, QEvent *e) { } else if (e->type() == QEvent::MouseButtonPress && static_cast(e)->button() == Qt::LeftButton/* && !dynamic_cast(obj)*/) { if (isHidden() || _hiding) { _hideTimer.stop(); - showStart(); + showAnimated(); } else { hideAnimated(); } @@ -3317,7 +3317,7 @@ void EmojiPan::stickersInstalled(uint64 setId) { showAll(); s_inner.showStickerSet(setId); updateContentHeight(); - showStart(); + showAnimated(); } void EmojiPan::notify_inlineItemLayoutChanged(const InlineBots::Layout::ItemBase *layout) { @@ -3490,7 +3490,7 @@ void EmojiPan::validateSelectedIcon(ValidateIconAnimations animations) { void EmojiPan::onSwitch() { QPixmap cache = _cache; - _fromCache = myGrab(this, rect().marginsRemoved(st::dropdownDef.padding)); + _fromCache = myGrab(this, rect().marginsRemoved(st::defaultDropdownPadding)); _stickersShown = !_stickersShown; if (!_stickersShown) { Notify::clipStopperHidden(ClipStopperSavedGifsPanel); @@ -3515,7 +3515,7 @@ void EmojiPan::onSwitch() { _cache = QPixmap(); showAll(); - _toCache = myGrab(this, rect().marginsRemoved(st::dropdownDef.padding)); + _toCache = myGrab(this, rect().marginsRemoved(st::defaultDropdownPadding)); _cache = cache; hideAll(); @@ -3804,7 +3804,7 @@ int32 EmojiPan::showInlineRows(bool newResults) { } else { _hideTimer.stop(); if (hidden || _hiding) { - showStart(); + showAnimated(); } else if (!_stickersShown) { onSwitch(); } diff --git a/Telegram/SourceFiles/stickers/emoji_pan.h b/Telegram/SourceFiles/stickers/emoji_pan.h index 488f5c86f..a90ae4d1e 100644 --- a/Telegram/SourceFiles/stickers/emoji_pan.h +++ b/Telegram/SourceFiles/stickers/emoji_pan.h @@ -67,21 +67,20 @@ public: void step_appearance(float64 ms, bool timer); void step_selected(uint64 ms, bool timer); - void showStart(); void clearSelection(bool fast = false); -public slots: + void hideFast(); - void hideStart(bool fast = false); +public slots: + void showAnimated(); + void hideAnimated(); signals: - void emojiSelected(EmojiPtr emoji); void hidden(); private: - void drawVariant(Painter &p, int variant); void updateSelected(); @@ -113,7 +112,7 @@ private: }; class EmojiPanel; -class EmojiPanInner : public ScrolledWidget { +class EmojiPanInner : public TWidget { Q_OBJECT public: @@ -207,7 +206,7 @@ struct StickerIcon { int pixh = 0; }; -class StickerPanInner : public ScrolledWidget, private base::Subscriber { +class StickerPanInner : public TWidget, private base::Subscriber { Q_OBJECT public: @@ -499,7 +498,7 @@ public: bool event(QEvent *e); - void fastHide(); + void hideFast(); bool hiding() const { return _hiding || _hideTimer.isActive(); } @@ -517,10 +516,10 @@ public: bool overlaps(const QRect &globalRect) { if (isHidden() || !_cache.isNull()) return false; - return QRect(st::dropdownDef.padding.left(), - st::dropdownDef.padding.top(), - _width - st::dropdownDef.padding.left() - st::dropdownDef.padding.right(), - _height - st::dropdownDef.padding.top() - st::dropdownDef.padding.bottom() + return QRect(st::defaultDropdownPadding.left(), + st::defaultDropdownPadding.top(), + _width - st::defaultDropdownPadding.left() - st::defaultDropdownPadding.right(), + _height - st::defaultDropdownPadding.top() - st::defaultDropdownPadding.bottom() ).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); } @@ -534,7 +533,9 @@ public: } public slots: - void hideStart(); + void showAnimated(); + void hideAnimated(); + void refreshStickers(); private slots: @@ -542,7 +543,6 @@ private slots: void hideFinish(); - void showStart(); void onWndActiveChanged(); void onTabChange(); @@ -590,7 +590,7 @@ private: void updateContentHeight(); void leaveToChildEvent(QEvent *e, QWidget *child); - void hideAnimated(); + void startHideAnimated(); void prepareShowHideCache(); void updateSelected(); diff --git a/Telegram/SourceFiles/ui/buttons/history_down_button.cpp b/Telegram/SourceFiles/ui/buttons/history_down_button.cpp index 773742426..f994f7f27 100644 --- a/Telegram/SourceFiles/ui/buttons/history_down_button.cpp +++ b/Telegram/SourceFiles/ui/buttons/history_down_button.cpp @@ -27,7 +27,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Ui { HistoryDownButton::HistoryDownButton(QWidget *parent) : Button(parent) -, a_arrowOpacity(st::btnAttachEmoji.opacity, st::btnAttachEmoji.opacity) +, a_arrowOpacity(st::historyAttachEmoji.opacity, st::historyAttachEmoji.opacity) , _a_arrowOver(animation(this, &HistoryDownButton::step_arrowOver)) { setCursor(style::cur_pointer); @@ -83,7 +83,7 @@ void HistoryDownButton::paintEvent(QPaintEvent *e) { } void HistoryDownButton::onStateChanged(int oldState, ButtonStateChangeSource source) { - a_arrowOpacity.start((_state & (StateOver | StateDown)) ? st::btnAttachEmoji.overOpacity : st::btnAttachEmoji.opacity); + a_arrowOpacity.start((_state & (StateOver | StateDown)) ? st::historyAttachEmoji.overOpacity : st::historyAttachEmoji.opacity); if (source == ButtonByUser || source == ButtonByPress) { _a_arrowOver.stop(); @@ -118,7 +118,7 @@ void HistoryDownButton::hideAnimated() { void HistoryDownButton::toggleAnimated() { _shown = !_shown; float64 from = _shown ? 0. : 1., to = _shown ? 1. : 0.; - _a_show.start([this] { update(); }, from, to, st::btnAttachEmoji.duration); + _a_show.start([this] { update(); }, from, to, st::historyAttachEmoji.duration); } void HistoryDownButton::finishAnimation() { @@ -127,7 +127,7 @@ void HistoryDownButton::finishAnimation() { } void HistoryDownButton::step_arrowOver(float64 ms, bool timer) { - float64 dt = ms / st::btnAttachEmoji.duration; + float64 dt = ms / st::historyAttachEmoji.duration; if (dt >= 1) { _a_arrowOver.stop(); a_arrowOpacity.finish(); @@ -137,4 +137,64 @@ void HistoryDownButton::step_arrowOver(float64 ms, bool timer) { if (timer) update(); } +EmojiButton::EmojiButton(QWidget *parent, const style::IconButton &st) : Button(parent) +, _st(st) +, _a_loading(animation(this, &EmojiButton::step_loading)) { + resize(_st.width, _st.height); + setCursor(style::cur_pointer); +} + +void EmojiButton::paintEvent(QPaintEvent *e) { + Painter p(this); + + uint64 ms = getms(); + + p.fillRect(e->rect(), st::white); + + auto over = _a_over.current(getms(), (_state & StateOver) ? 1. : 0.); + auto opacity = over * _st.overOpacity + (1. - over) * _st.opacity; + + auto loading = a_loading.current(ms, _loading ? 1 : 0); + p.setOpacity(opacity * (1 - loading)); + + _st.icon.paint(p, (_state & StateDown) ? _st.downIconPosition : _st.iconPosition, width()); + + p.setOpacity(opacity); + p.setPen(QPen(st::historyEmojiCircleFg, st::historyEmojiCircleLine)); + p.setBrush(Qt::NoBrush); + + p.setRenderHint(QPainter::HighQualityAntialiasing); + QRect inner(QPoint((width() - st::historyEmojiCircle.width()) / 2, st::historyEmojiCircleTop), st::historyEmojiCircle); + if (loading > 0) { + int32 full = 5760; + int32 start = qRound(full * float64(ms % uint64(st::historyEmojiCirclePeriod)) / st::historyEmojiCirclePeriod), part = qRound(loading * full / st::historyEmojiCirclePart); + p.drawArc(inner, start, full - part); + } else { + p.drawEllipse(inner); + } + p.setRenderHint(QPainter::HighQualityAntialiasing, false); +} + +void EmojiButton::setLoading(bool loading) { + if (_loading != loading) { + _loading = loading; + auto from = loading ? 0. : 1., to = loading ? 1. : 0.; + a_loading.start([this] { update(); }, from, to, st::historyEmojiCircleDuration); + if (loading) { + _a_loading.start(); + } else { + _a_loading.stop(); + } + } +} + +void EmojiButton::onStateChanged(int oldState, ButtonStateChangeSource source) { + auto over = (_state & StateOver); + if (over != (oldState & StateOver)) { + auto from = over ? 0. : 1.; + auto to = over ? 1. : 0.; + _a_over.start([this] { update(); }, from, to, _st.duration); + } +} + } // namespace Ui diff --git a/Telegram/SourceFiles/ui/buttons/history_down_button.h b/Telegram/SourceFiles/ui/buttons/history_down_button.h index 8030a905e..0ab821ee5 100644 --- a/Telegram/SourceFiles/ui/buttons/history_down_button.h +++ b/Telegram/SourceFiles/ui/buttons/history_down_button.h @@ -61,4 +61,31 @@ private: }; +class EmojiButton : public Button { +public: + EmojiButton(QWidget *parent, const style::IconButton &st); + + void setLoading(bool loading); + +protected: + void paintEvent(QPaintEvent *e) override; + void onStateChanged(int oldState, ButtonStateChangeSource source) override; + +private: + const style::IconButton &_st; + + FloatAnimation _a_over; + + bool _loading = false; + FloatAnimation a_loading; + Animation _a_loading; + + void step_loading(uint64 ms, bool timer) { + if (timer) { + update(); + } + } + +}; + } // namespace Ui diff --git a/Telegram/SourceFiles/ui/countryinput.cpp b/Telegram/SourceFiles/ui/countryinput.cpp index c4a390e9c..ff146b3bc 100644 --- a/Telegram/SourceFiles/ui/countryinput.cpp +++ b/Telegram/SourceFiles/ui/countryinput.cpp @@ -261,10 +261,8 @@ void CountrySelectBox::doSetInnerFocus() { _select->setInnerFocus(); } -CountrySelectBox::Inner::Inner(QWidget *parent) : ScrolledWidget(parent) -, _rowHeight(st::countryRowHeight) -, _sel(0) -, _mouseSel(false) { +CountrySelectBox::Inner::Inner(QWidget *parent) : TWidget(parent) +, _rowHeight(st::countryRowHeight) { setAttribute(Qt::WA_OpaquePaintEvent); CountriesByISO2::const_iterator l = _countriesByISO2.constFind(lastValidISO); diff --git a/Telegram/SourceFiles/ui/countryinput.h b/Telegram/SourceFiles/ui/countryinput.h index fe3b217a9..e9a72d257 100644 --- a/Telegram/SourceFiles/ui/countryinput.h +++ b/Telegram/SourceFiles/ui/countryinput.h @@ -103,7 +103,7 @@ private: }; // This class is hold in header because it requires Qt preprocessing. -class CountrySelectBox::Inner : public ScrolledWidget { +class CountrySelectBox::Inner : public TWidget { Q_OBJECT public: @@ -135,11 +135,11 @@ protected: private: void updateSelectedRow(); - int32 _rowHeight; + int _rowHeight; - int32 _sel; + int _sel = 0; QString _filter; - bool _mouseSel; + bool _mouseSel = false; QPoint _lastMousePos; diff --git a/Telegram/SourceFiles/ui/flatbutton.cpp b/Telegram/SourceFiles/ui/flatbutton.cpp index 167d30f86..7fcc8d8b3 100644 --- a/Telegram/SourceFiles/ui/flatbutton.cpp +++ b/Telegram/SourceFiles/ui/flatbutton.cpp @@ -21,6 +21,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "ui/flatbutton.h" +#include "styles/style_history.h" + FlatButton::FlatButton(QWidget *parent, const QString &text, const style::flatButton &st) : Button(parent) , _text(text) , _st(st) @@ -264,83 +266,6 @@ void IconedButton::paintEvent(QPaintEvent *e) { } } -MaskedButton::MaskedButton(QWidget *parent, const style::iconedButton &st, const QString &text) : IconedButton(parent, st, text) { -} - -void MaskedButton::paintEvent(QPaintEvent *e) { - Painter p(this); - - p.setOpacity(_opacity); - - p.setOpacity(a_opacity.current() * _opacity); - - if (!_text.isEmpty()) { - p.setFont(_st.font->f); - p.setRenderHint(QPainter::TextAntialiasing); - p.setPen(a_bg.current()); - const QPoint &t((_state & StateDown) ? _st.downTextPos : _st.textPos); - p.drawText(t.x(), t.y() + _st.font->ascent, _text); - } - - const style::sprite &i((_state & StateDown) ? _st.downIcon : _st.icon); - if (i.pxWidth()) { - const QPoint &t((_state & StateDown) ? _st.downIconPos : _st.iconPos); - p.fillRect(QRect(t, QSize(i.pxWidth(), i.pxHeight())), a_bg.current()); - p.drawSprite(t, i); - } -} - -EmojiButton::EmojiButton(QWidget *parent, const style::iconedButton &st) : IconedButton(parent, st) -, _loading(false) -, _a_loading(animation(this, &EmojiButton::step_loading)) { -} - -void EmojiButton::paintEvent(QPaintEvent *e) { - Painter p(this); - - uint64 ms = getms(); - float64 loading = a_loading.current(ms, _loading ? 1 : 0); - p.setOpacity(_opacity * (1 - loading)); - - p.fillRect(e->rect(), a_bg.current()); - - p.setOpacity(a_opacity.current() * _opacity * (1 - loading)); - - const style::sprite &i((_state & StateDown) ? _st.downIcon : _st.icon); - if (!i.isEmpty()) { - const QPoint &t((_state & StateDown) ? _st.downIconPos : _st.iconPos); - p.drawSprite(t, i); - } - - p.setOpacity(a_opacity.current() * _opacity); - p.setPen(QPen(st::emojiCircleFg, st::emojiCircleLine)); - p.setBrush(Qt::NoBrush); - - p.setRenderHint(QPainter::HighQualityAntialiasing); - QRect inner(QPoint((width() - st::emojiCircle.width()) / 2, st::emojiCircleTop), st::emojiCircle); - if (loading > 0) { - int32 full = 5760; - int32 start = qRound(full * float64(ms % uint64(st::emojiCirclePeriod)) / st::emojiCirclePeriod), part = qRound(loading * full / st::emojiCirclePart); - p.drawArc(inner, start, full - part); - } else { - p.drawEllipse(inner); - } - p.setRenderHint(QPainter::HighQualityAntialiasing, false); -} - -void EmojiButton::setLoading(bool loading) { - if (_loading != loading) { - _loading = loading; - auto from = loading ? 0. : 1., to = loading ? 1. : 0.; - a_loading.start([this] { update(); }, from, to, st::emojiCircleDuration); - if (loading) { - _a_loading.start(); - } else { - _a_loading.stop(); - } - } -} - BoxButton::BoxButton(QWidget *parent, const QString &text, const style::RoundButton &st) : Button(parent) , _text(text.toUpper()) , _fullText(text.toUpper()) diff --git a/Telegram/SourceFiles/ui/flatbutton.h b/Telegram/SourceFiles/ui/flatbutton.h index 090b3232f..643836c66 100644 --- a/Telegram/SourceFiles/ui/flatbutton.h +++ b/Telegram/SourceFiles/ui/flatbutton.h @@ -28,7 +28,6 @@ class FlatButton : public Button { Q_OBJECT public: - FlatButton(QWidget *parent, const QString &text, const style::flatButton &st); void step_appearance(float64 ms, bool timer); @@ -45,11 +44,9 @@ public: } public slots: - void onStateChange(int oldState, ButtonStateChangeSource source); private: - QString _text, _textForAutoSize; int32 _textWidth; @@ -91,7 +88,6 @@ class IconedButton : public Button { Q_OBJECT public: - IconedButton(QWidget *parent, const style::iconedButton &st, const QString &text = QString()); void step_appearance(float64 ms, bool timer); @@ -103,11 +99,9 @@ public: QString getText() const; public slots: - void onStateChange(int oldState, ButtonStateChangeSource source); protected: - QString _text; style::iconedButton _st; @@ -120,39 +114,6 @@ protected: float64 _opacity; }; -class MaskedButton : public IconedButton { - Q_OBJECT - -public: - - MaskedButton(QWidget *parent, const style::iconedButton &st, const QString &text = QString()); - - void paintEvent(QPaintEvent *e); - -}; - -class EmojiButton : public IconedButton { - Q_OBJECT - -public: - EmojiButton(QWidget *parent, const style::iconedButton &st); - - void paintEvent(QPaintEvent *e); - void setLoading(bool loading); - -private: - bool _loading; - FloatAnimation a_loading; - Animation _a_loading; - - void step_loading(uint64 ms, bool timer) { - if (timer) { - update(); - } - } - -}; - class BoxButton : public Button { Q_OBJECT diff --git a/Telegram/SourceFiles/ui/flatinput.cpp b/Telegram/SourceFiles/ui/flatinput.cpp index d950fc5fd..2829086ca 100644 --- a/Telegram/SourceFiles/ui/flatinput.cpp +++ b/Telegram/SourceFiles/ui/flatinput.cpp @@ -21,7 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "ui/flatinput.h" -#include "ui/popupmenu.h" +#include "ui/widgets/popup_menu.h" #include "mainwindow.h" #include "countryinput.h" #include "lang.h" @@ -244,7 +244,12 @@ void FlatInput::updatePlaceholderText() { void FlatInput::contextMenuEvent(QContextMenuEvent *e) { if (auto menu = createStandardContextMenu()) { - (new PopupMenu(menu))->popup(e->globalPos()); + menu->addSeparator(); + auto action = menu->addAction(QString("test")); + action->setMenu(new QMenu(this)); + action->menu()->addAction(QString("test123")); + action->menu()->addAction(QString("test456")); + (new Ui::PopupMenu(menu))->popup(e->globalPos()); } } @@ -1282,7 +1287,7 @@ void InputArea::Inner::paintEvent(QPaintEvent *e) { void InputArea::Inner::contextMenuEvent(QContextMenuEvent *e) { if (auto menu = createStandardContextMenu()) { - (new PopupMenu(menu))->popup(e->globalPos()); + (new Ui::PopupMenu(menu))->popup(e->globalPos()); } } @@ -2029,7 +2034,7 @@ void InputField::Inner::paintEvent(QPaintEvent *e) { void InputField::Inner::contextMenuEvent(QContextMenuEvent *e) { if (auto menu = createStandardContextMenu()) { - (new PopupMenu(menu))->popup(e->globalPos()); + (new Ui::PopupMenu(menu))->popup(e->globalPos()); } } @@ -2237,7 +2242,7 @@ void MaskedInputField::updatePlaceholderText() { void MaskedInputField::contextMenuEvent(QContextMenuEvent *e) { if (auto menu = createStandardContextMenu()) { - (new PopupMenu(menu))->popup(e->globalPos()); + (new Ui::PopupMenu(menu))->popup(e->globalPos()); } } diff --git a/Telegram/SourceFiles/ui/flatlabel.cpp b/Telegram/SourceFiles/ui/flatlabel.cpp index e4d0a7d8b..6be2345ea 100644 --- a/Telegram/SourceFiles/ui/flatlabel.cpp +++ b/Telegram/SourceFiles/ui/flatlabel.cpp @@ -21,7 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "ui/flatlabel.h" -#include "ui/popupmenu.h" +#include "ui/widgets/popup_menu.h" #include "mainwindow.h" #include "lang.h" @@ -414,7 +414,7 @@ void FlatLabel::showContextMenu(QContextMenuEvent *e, ContextMenuReason reason) uponSelection = hasSelection; } - _contextMenu = new PopupMenu(); + _contextMenu = new Ui::PopupMenu(); _contextMenuClickHandler = ClickHandler::getActive(); diff --git a/Telegram/SourceFiles/ui/flatlabel.h b/Telegram/SourceFiles/ui/flatlabel.h index b84d775e7..4c16382bb 100644 --- a/Telegram/SourceFiles/ui/flatlabel.h +++ b/Telegram/SourceFiles/ui/flatlabel.h @@ -20,7 +20,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once +namespace Ui { class PopupMenu; +} // namespace Ui class FlatLabel : public TWidget, public ClickHandlerHost { Q_OBJECT @@ -137,7 +139,7 @@ private: QPoint _trippleClickPoint; QTimer _trippleClickTimer; - PopupMenu *_contextMenu = nullptr; + Ui::PopupMenu *_contextMenu = nullptr; ClickHandlerPtr _contextMenuClickHandler; QString _contextCopyText; ExpandLinksMode _contextExpandLinksMode = ExpandLinksAll; diff --git a/Telegram/SourceFiles/ui/flattextarea.cpp b/Telegram/SourceFiles/ui/flattextarea.cpp index e1445775e..8b78905cd 100644 --- a/Telegram/SourceFiles/ui/flattextarea.cpp +++ b/Telegram/SourceFiles/ui/flattextarea.cpp @@ -21,7 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "flattextarea.h" -#include "ui/popupmenu.h" +#include "ui/widgets/popup_menu.h" #include "mainwindow.h" QByteArray FlatTextarea::serializeTagsList(const TagList &tags) { @@ -1421,6 +1421,6 @@ void FlatTextarea::dropEvent(QDropEvent *e) { void FlatTextarea::contextMenuEvent(QContextMenuEvent *e) { if (auto menu = createStandardContextMenu()) { - (new PopupMenu(menu))->popup(e->globalPos()); + (new Ui::PopupMenu(menu))->popup(e->globalPos()); } } diff --git a/Telegram/SourceFiles/ui/popupmenu.cpp b/Telegram/SourceFiles/ui/popupmenu.cpp deleted file mode 100644 index 3bb06e31f..000000000 --- a/Telegram/SourceFiles/ui/popupmenu.cpp +++ /dev/null @@ -1,678 +0,0 @@ -/* - This file is part of Telegram Desktop, - the official desktop version of Telegram messaging app, see https://telegram.org - - Telegram Desktop is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - It is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE - Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org - */ -#include "stdafx.h" - -#include "popupmenu.h" -#include "flatbutton.h" -#include "pspecific.h" - -#include "application.h" - -#include "lang.h" - -PopupMenu::PopupMenu(const style::PopupMenu &st) : TWidget(nullptr) -, _st(st) -, _itemHeight(_st.itemPadding.top() + _st.itemFont->height + _st.itemPadding.bottom()) -, _separatorHeight(_st.separatorPadding.top() + _st.separatorWidth + _st.separatorPadding.bottom()) -, _shadow(_st.shadow) -, a_opacity(1) -, _a_hide(animation(this, &PopupMenu::step_hide)) { - init(); -} - -PopupMenu::PopupMenu(QMenu *menu, const style::PopupMenu &st) : TWidget(nullptr) -, _st(st) -, _menu(menu) -, _itemHeight(_st.itemPadding.top() + _st.itemFont->height + _st.itemPadding.bottom()) -, _separatorHeight(_st.separatorPadding.top() + _st.separatorWidth + _st.separatorPadding.bottom()) -, _shadow(_st.shadow) -, a_opacity(1) -, _a_hide(animation(this, &PopupMenu::step_hide)) { - _menu->setParent(this); - _menu->hide(); - - init(); - for (auto action : menu->actions()) { - addAction(action); - } -} - -void PopupMenu::init() { - _padding = _shadow.getDimensions(_st.shadowShift); - - resetActions(); - - setWindowFlags(Qt::FramelessWindowHint | Qt::BypassWindowManagerHint | Qt::Popup | Qt::NoDropShadowWindowHint); - setMouseTracking(true); - - hide(); - - setAttribute(Qt::WA_NoSystemBackground, true); - setAttribute(Qt::WA_TranslucentBackground, true); -} - -QAction *PopupMenu::addAction(const QString &text, const QObject *receiver, const char* member) { - QAction *a = new QAction(text, this); - connect(a, SIGNAL(triggered(bool)), receiver, member, Qt::QueuedConnection); - return addAction(a); -} - -QAction *PopupMenu::addAction(QAction *a) { - connect(a, SIGNAL(changed()), this, SLOT(actionChanged())); - _actions.push_back(a); - if (auto submenu = a->menu()) { - _menus.push_back(new PopupMenu(submenu)); - _menus.back()->deleteOnHide(false); - } else { - _menus.push_back(nullptr); - } - _texts.push_back(QString()); - _shortcutTexts.push_back(QString()); - int32 w = processAction(a, _actions.size() - 1, width()); - resize(w, height() + (a->isSeparator() ? _separatorHeight : _itemHeight)); - update(); - - return a; -} - -QAction *PopupMenu::addSeparator() { - QAction *separator = new QAction(this); - separator->setSeparator(true); - return addAction(separator); -} - -int32 PopupMenu::processAction(QAction *a, int32 index, int32 w) { - if (a->isSeparator() || a->text().isEmpty()) { - _texts[index] = _shortcutTexts[index] = QString(); - } else { - QStringList texts = a->text().split('\t'); - int32 textw = _st.itemFont->width(texts.at(0)); - int32 goodw = _padding.left() + _st.itemPadding.left() + textw + _st.itemPadding.right() + _padding.right(); - if (_menus.at(index)) { - goodw += _st.itemPadding.left() + _st.arrow.width(); - } else if (texts.size() > 1) { - goodw += _st.itemPadding.left() + _st.itemFont->width(texts.at(1)); - } - w = snap(goodw, w, int32(_padding.left() + _st.widthMax + _padding.right())); - _texts[index] = (w < goodw) ? _st.itemFont->elided(texts.at(0), w - (goodw - textw)) : texts.at(0); - _shortcutTexts[index] = texts.size() > 1 ? texts.at(1) : QString(); - } - return w; -} - -PopupMenu::Actions &PopupMenu::actions() { - return _actions; -} - -void PopupMenu::actionChanged() { - int32 w = _padding.left() + _st.widthMin + _padding.right(); - for (int32 i = 0, l = _actions.size(); i < l; ++i) { - w = processAction(_actions.at(i), i, w); - } - if (w != width()) { - resize(w, height()); - } - update(); -} - -void PopupMenu::resetActions() { - clearActions(); - resize(_padding.left() + _st.widthMin + _padding.right(), _padding.top() + (_st.skip * 2) + _padding.bottom()); -} - -void PopupMenu::clearActions(bool force) { - if (_menu && !force) return; - - if (!_menu) { - for (int32 i = 0, l = _actions.size(); i < l; ++i) { - delete _actions[i]; - } - } - _actions.clear(); - - for (int32 i = 0, l = _menus.size(); i < l; ++i) { - delete _menus[i]; - } - _menus.clear(); - _childMenuIndex = -1; - - _selected = -1; -} - -void PopupMenu::resizeEvent(QResizeEvent *e) { - _inner = QRect(_padding.left(), _padding.top(), width() - _padding.left() - _padding.right(), height() - _padding.top() - _padding.bottom()); - return TWidget::resizeEvent(e); -} - -void PopupMenu::paintEvent(QPaintEvent *e) { - Painter p(this); - - QRect r(e->rect()); - p.setClipRect(r); - QPainter::CompositionMode m = p.compositionMode(); - p.setCompositionMode(QPainter::CompositionMode_Source); - if (_a_hide.animating()) { - p.setOpacity(a_opacity.current()); - p.drawPixmap(0, 0, _cache); - return; - } - - p.fillRect(r, st::almostTransparent->b); - p.setCompositionMode(m); - - _shadow.paint(p, _inner, _st.shadowShift); - - QRect topskip(_padding.left(), _padding.top(), _inner.width(), _st.skip); - QRect bottomskip(_padding.left(), height() - _padding.bottom() - _st.skip, _inner.width(), _st.skip); - if (r.intersects(topskip)) p.fillRect(r.intersected(topskip), _st.itemBg->b); - if (r.intersects(bottomskip)) p.fillRect(r.intersected(bottomskip), _st.itemBg->b); - - int32 y = _padding.top() + _st.skip; - p.translate(_padding.left(), y); - p.setFont(_st.itemFont); - for (int32 i = 0, l = _actions.size(); i < l; ++i) { - if (r.top() + r.height() <= y) break; - int32 h = _actions.at(i)->isSeparator() ? _separatorHeight : _itemHeight; - y += h; - if (r.top() < y) { - if (_actions.at(i)->isSeparator()) { - p.fillRect(0, 0, _inner.width(), h, _st.itemBg->b); - p.fillRect(_st.separatorPadding.left(), _st.separatorPadding.top(), _inner.width() - _st.separatorPadding.left() - _st.separatorPadding.right(), _st.separatorWidth, _st.separatorFg->b); - } else { - bool enabled = _actions.at(i)->isEnabled(), selected = (i == _selected && enabled); - p.fillRect(0, 0, _inner.width(), h, (selected ? _st.itemBgOver : _st.itemBg)->b); - p.setPen(selected ? _st.itemFgOver : (enabled ? _st.itemFg : _st.itemFgDisabled)); - p.drawTextLeft(_st.itemPadding.left(), _st.itemPadding.top(), _inner.width(), _texts.at(i)); - if (_menus.at(i)) { - _st.arrow.paint(p, _inner.width() - _st.itemPadding.right() - _st.arrow.width(), (_itemHeight - _st.arrow.height()) / 2, _inner.width()); - } else if (!_shortcutTexts.at(i).isEmpty()) { - p.setPen(selected ? _st.itemFgShortcutOver : (enabled ? _st.itemFgShortcut : _st.itemFgShortcutDisabled)); - p.drawTextRight(_st.itemPadding.right(), _st.itemPadding.top(), _inner.width(), _shortcutTexts.at(i)); - } - } - } - p.translate(0, h); - } -} - -void PopupMenu::updateSelected() { - if (!_mouseSelection) return; - - QPoint p(mapFromGlobal(_mouse) - QPoint(_padding.left(), _padding.top() + _st.skip)); - int32 selected = -1, y = 0; - while (y <= p.y() && ++selected < _actions.size()) { - y += _actions.at(selected)->isSeparator() ? _separatorHeight : _itemHeight; - } - setSelected((selected >= 0 && selected < _actions.size() && _actions.at(selected)->isEnabled() && !_actions.at(selected)->isSeparator()) ? selected : -1); -} - -void PopupMenu::itemPressed(PressSource source) { - if (source == PressSourceMouse && !_mouseSelection) { - return; - } - if (_selected >= 0 && _selected < _actions.size() && _actions[_selected]->isEnabled()) { - if (_menus.at(_selected)) { - if (_childMenuIndex == _selected) { - _menus.at(_childMenuIndex)->hideMenu(true); - } else { - popupChildMenu(source); - } - } else { - hideMenu(); - _triggering = true; - emit _actions[_selected]->trigger(); - _triggering = false; - if (_deleteLater) { - _deleteLater = false; - deleteLater(); - } - } - } -} - -void PopupMenu::popupChildMenu(PressSource source) { - if (_childMenuIndex >= 0) { - _menus.at(_childMenuIndex)->hideMenu(true); - _childMenuIndex = -1; - } - if (_selected >= 0 && _selected < _menus.size() && _menus.at(_selected)) { - QPoint p(_inner.x() + (rtl() ? _padding.right() : _inner.width() - _padding.left()), _inner.y() + _st.skip + itemY(_selected)); - _childMenuIndex = _selected; - _menus.at(_childMenuIndex)->showMenu(geometry().topLeft() + p, this, source); - } -} - -void PopupMenu::keyPressEvent(QKeyEvent *e) { - if (_childMenuIndex >= 0) { - return _menus.at(_childMenuIndex)->keyPressEvent(e); - } - - if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { - itemPressed(PressSourceKeyboard); - return; - } else if (e->key() == Qt::Key_Escape) { - hideMenu(_parent ? true : false); - return; - } - if (e->key() == (rtl() ? Qt::Key_Left : Qt::Key_Right)) { - if (_selected >= 0 && _menus.at(_selected)) { - itemPressed(PressSourceKeyboard); - return; - } else if (_selected < 0 && _parent && !_actions.isEmpty()) { - _mouseSelection = false; - setSelected(0); - } - } else if (e->key() == (rtl() ? Qt::Key_Right : Qt::Key_Left)) { - if (_parent) { - hideMenu(true); - } - } - if ((e->key() != Qt::Key_Up && e->key() != Qt::Key_Down) || _actions.size() < 1) return; - - int32 delta = (e->key() == Qt::Key_Down ? 1 : -1), start = _selected; - if (start < 0 || start >= _actions.size()) { - start = (delta > 0) ? (_actions.size() - 1) : 0; - } - int32 newSelected = start; - do { - newSelected += delta; - if (newSelected < 0) { - newSelected += _actions.size(); - } else if (newSelected >= _actions.size()) { - newSelected -= _actions.size(); - } - } while (newSelected != start && (!_actions.at(newSelected)->isEnabled() || _actions.at(newSelected)->isSeparator())); - - if (_actions.at(newSelected)->isEnabled() && !_actions.at(newSelected)->isSeparator()) { - _mouseSelection = false; - setSelected(newSelected); - } -} - -void PopupMenu::enterEvent(QEvent *e) { - QPoint mouse = QCursor::pos(); - if (!_inner.marginsRemoved(QMargins(0, _st.skip, 0, _st.skip)).contains(mapFromGlobal(mouse))) { - if (_mouseSelection && _childMenuIndex < 0) { - _mouseSelection = false; - setSelected(-1); - } - } - return TWidget::enterEvent(e); -} - -void PopupMenu::leaveEvent(QEvent *e) { - if (_mouseSelection && _childMenuIndex < 0) { - _mouseSelection = false; - setSelected(-1); - } - return TWidget::leaveEvent(e); -} - -void PopupMenu::setSelected(int32 newSelected) { - if (newSelected >= _actions.size()) { - newSelected = -1; - } - if (newSelected != _selected) { - updateSelectedItem(); - _selected = newSelected; - if (_mouseSelection) { - popupChildMenu(PressSourceMouse); - } - updateSelectedItem(); - } -} - -int32 PopupMenu::itemY(int32 index) { - if (index > _actions.size()) { - index = _actions.size(); - } - int32 y = 0; - for (int32 i = 0; i < index; ++i) { - y += _actions.at(i)->isSeparator() ? _separatorHeight : _itemHeight; - } - return y; -} - -void PopupMenu::updateSelectedItem() { - if (_selected >= 0) { - update(_padding.left(), _padding.top() + _st.skip + itemY(_selected), width() - _padding.left() - _padding.right(), _actions.at(_selected)->isSeparator() ? _separatorHeight : _itemHeight); - } -} - -void PopupMenu::mouseMoveEvent(QMouseEvent *e) { - if (_inner.marginsRemoved(QMargins(0, _st.skip, 0, _st.skip)).contains(mapFromGlobal(e->globalPos()))) { - _mouseSelection = true; - _mouse = e->globalPos(); - updateSelected(); - } else { - if (_mouseSelection && _childMenuIndex < 0) { - _mouseSelection = false; - setSelected(-1); - } - if (_parent) { - _parent->mouseMoveEvent(e); - } - } -} - -void PopupMenu::mousePressEvent(QMouseEvent *e) { - mouseMoveEvent(e); - if (_inner.contains(mapFromGlobal(e->globalPos()))) { - itemPressed(PressSourceMouse); - return; - } - if (_parent) { - _parent->mousePressEvent(e); - } else { - hideMenu(); - } -} - -void PopupMenu::focusOutEvent(QFocusEvent *e) { - hideMenu(); -} - -void PopupMenu::hideEvent(QHideEvent *e) { - if (_deleteOnHide) { - if (_triggering) { - _deleteLater = true; - } else { - deleteLater(); - } - } -} - -void PopupMenu::hideMenu(bool fast) { - if (isHidden()) return; - if (_parent && !_a_hide.animating()) { - _parent->childHiding(this); - } - if (fast) { - if (_a_hide.animating()) { - _a_hide.stop(); - } - a_opacity = anim::fvalue(0, 0); - hideFinish(); - } else { - if (!_a_hide.animating()) { - _cache = myGrab(this); - a_opacity.start(0); - _a_hide.start(); - } - if (_parent) { - _parent->hideMenu(); - } - } - if (_childMenuIndex >= 0) { - _menus.at(_childMenuIndex)->hideMenu(fast); - } -} - -void PopupMenu::childHiding(PopupMenu *child) { - if (_childMenuIndex >= 0 && _menus.at(_childMenuIndex) == child) { - _childMenuIndex = -1; - } -} - -void PopupMenu::hideFinish() { - hide(); -} - -void PopupMenu::step_hide(float64 ms, bool timer) { - float64 dt = ms / _st.duration; - if (dt >= 1) { - _a_hide.stop(); - a_opacity.finish(); - hideFinish(); - } else { - a_opacity.update(dt, anim::linear); - } - if (timer) update(); -} - -void PopupMenu::deleteOnHide(bool del) { - _deleteOnHide = del; -} - -void PopupMenu::popup(const QPoint &p) { - showMenu(p, 0, PressSourceMouse); -} - -void PopupMenu::showMenu(const QPoint &p, PopupMenu *parent, PressSource source) { - _parent = parent; - - QPoint w = p - QPoint(0, _padding.top()); - QRect r = Sandbox::screenGeometry(p); - if (rtl()) { - if (w.x() - width() < r.x() - _padding.left()) { - if (_parent && w.x() + _parent->width() - _padding.left() - _padding.right() + width() - _padding.right() <= r.x() + r.width()) { - w.setX(w.x() + _parent->width() - _padding.left() - _padding.right()); - } else { - w.setX(r.x() - _padding.left()); - } - } else { - w.setX(w.x() - width()); - } - } else { - if (w.x() + width() - _padding.right() > r.x() + r.width()) { - if (_parent && w.x() - _parent->width() + _padding.left() + _padding.right() - width() + _padding.right() >= r.x() - _padding.left()) { - w.setX(w.x() + _padding.left() + _padding.right() - _parent->width() - width() + _padding.left() + _padding.right()); - } else { - w.setX(r.x() + r.width() - width() + _padding.right()); - } - } - } - if (w.y() + height() - _padding.bottom() > r.y() + r.height()) { - if (_parent) { - w.setY(r.y() + r.height() - height() + _padding.bottom()); - } else { - w.setY(p.y() - height() + _padding.bottom()); - } - } - if (w.y() < r.y()) { - w.setY(r.y()); - } - move(w); - - _mouseSelection = (source == PressSourceMouse); - setSelected((source == PressSourceMouse || _actions.isEmpty()) ? -1 : 0); - psUpdateOverlayed(this); - show(); - psShowOverAll(this); - windowHandle()->requestActivate(); - activateWindow(); - - if (_a_hide.animating()) { - _a_hide.stop(); - _cache = QPixmap(); - } - a_opacity = anim::fvalue(1, 1); -} - -PopupMenu::~PopupMenu() { - clearActions(true); - -#if defined Q_OS_LINUX32 || defined Q_OS_LINUX64 - if (auto w = App::wnd()) { - w->onReActivate(); - QTimer::singleShot(200, w, SLOT(onReActivate())); - } -#endif -} - -PopupTooltip *PopupTooltipInstance = 0; - -AbstractTooltipShower::~AbstractTooltipShower() { - if (PopupTooltipInstance && PopupTooltipInstance->_shower == this) { - PopupTooltipInstance->_shower = 0; - } -} - -PopupTooltip::PopupTooltip() : TWidget(0) -, _shower(0) -, _st(0) { - PopupTooltipInstance = this; - - setWindowFlags(Qt::FramelessWindowHint | Qt::BypassWindowManagerHint | Qt::ToolTip | Qt::NoDropShadowWindowHint); - setAttribute(Qt::WA_NoSystemBackground, true); - - _showTimer.setSingleShot(true); - connect(&_showTimer, SIGNAL(timeout()), this, SLOT(onShow())); - - connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWndActiveChanged())); -} - -void PopupTooltip::onShow() { - if (_shower) { - QString text = (App::wnd() && App::wnd()->isActive(false)) ? _shower->tooltipText() : QString(); - if (text.isEmpty()) { - Hide(); - } else { - PopupTooltipInstance->popup(_shower->tooltipPos(), text, _shower->tooltipSt()); - } - } -} - -void PopupTooltip::onWndActiveChanged() { - if (!App::wnd() || !App::wnd()->windowHandle() || !App::wnd()->windowHandle()->isActive()) { - PopupTooltip::Hide(); - } -} - -bool PopupTooltip::eventFilter(QObject *o, QEvent *e) { - if (e->type() == QEvent::Leave) { - _hideByLeaveTimer.start(10); - } else if (e->type() == QEvent::Enter) { - _hideByLeaveTimer.stop(); - } else if (e->type() == QEvent::MouseMove) { - if ((QCursor::pos() - _point).manhattanLength() > QApplication::startDragDistance()) { - Hide(); - } - } - return TWidget::eventFilter(o, e); -} - -void PopupTooltip::onHideByLeave() { - Hide(); -} - -PopupTooltip::~PopupTooltip() { - if (PopupTooltipInstance == this) { - PopupTooltipInstance = 0; - } -} - -void PopupTooltip::popup(const QPoint &m, const QString &text, const style::Tooltip *st) { - if (!_hideByLeaveTimer.isSingleShot()) { - _hideByLeaveTimer.setSingleShot(true); - connect(&_hideByLeaveTimer, SIGNAL(timeout()), this, SLOT(onHideByLeave())); - - Sandbox::installEventFilter(this); - } - - _point = m; - _st = st; - _text = Text(_st->textFont, text, _textPlainOptions, _st->widthMax, true); - - int32 addw = 2 * st::lineWidth + _st->textPadding.left() + _st->textPadding.right(); - int32 addh = 2 * st::lineWidth + _st->textPadding.top() + _st->textPadding.bottom(); - - // count tooltip size - QSize s(addw + _text.maxWidth(), addh + _text.minHeight()); - if (s.width() > _st->widthMax) { - s.setWidth(addw + _text.countWidth(_st->widthMax - addw)); - s.setHeight(addh + _text.countHeight(s.width() - addw)); - } - int32 maxh = addh + (_st->linesMax * _st->textFont->height); - if (s.height() > maxh) { - s.setHeight(maxh); - } - - // count tooltip position - QPoint p(m + _st->shift); - if (rtl()) { - p.setX(m.x() - s.width() - _st->shift.x()); - } - if (s.width() < 2 * _st->shift.x()) { - p.setX(m.x() - (s.width() / 2)); - } - - // adjust tooltip position - QRect r(QApplication::desktop()->screenGeometry(m)); - if (r.x() + r.width() - _st->skip < p.x() + s.width() && p.x() + s.width() > m.x()) { - p.setX(qMax(r.x() + r.width() - int32(_st->skip) - s.width(), m.x() - s.width())); - } - if (r.x() + _st->skip > p.x() && p.x() < m.x()) { - p.setX(qMin(m.x(), r.x() + int32(_st->skip))); - } - if (r.y() + r.height() - _st->skip < p.y() + s.height()) { - p.setY(m.y() - s.height() - _st->skip); - } - if (r.y() > p.x()) { - p.setY(qMin(m.y() + _st->shift.y(), r.y() + r.height() - s.height())); - } - - setGeometry(QRect(p, s)); - - _hideByLeaveTimer.stop(); - show(); -} - -void PopupTooltip::paintEvent(QPaintEvent *e) { - Painter p(this); - - p.fillRect(rect(), _st->textBg); - - p.fillRect(QRect(0, 0, width(), st::lineWidth), _st->textBorder); - p.fillRect(QRect(0, height() - st::lineWidth, width(), st::lineWidth), _st->textBorder); - p.fillRect(QRect(0, st::lineWidth, st::lineWidth, height() - 2 * st::lineWidth), _st->textBorder); - p.fillRect(QRect(width() - st::lineWidth, st::lineWidth, st::lineWidth, height() - 2 * st::lineWidth), _st->textBorder); - - int32 lines = qFloor((height() - 2 * st::lineWidth - _st->textPadding.top() - _st->textPadding.bottom()) / _st->textFont->height); - - p.setPen(_st->textFg); - _text.drawElided(p, st::lineWidth + _st->textPadding.left(), st::lineWidth + _st->textPadding.top(), width() - 2 * st::lineWidth - _st->textPadding.left() - _st->textPadding.right(), lines); -} - -void PopupTooltip::hideEvent(QHideEvent *e) { - if (PopupTooltipInstance == this) { - Hide(); - } -} - -void PopupTooltip::Show(int32 delay, const AbstractTooltipShower *shower) { - if (!PopupTooltipInstance) { - new PopupTooltip(); - } - PopupTooltipInstance->_shower = shower; - if (delay >= 0) { - PopupTooltipInstance->_showTimer.start(delay); - } else { - PopupTooltipInstance->onShow(); - } -} - -void PopupTooltip::Hide() { - if (PopupTooltip *instance = PopupTooltipInstance) { - PopupTooltipInstance = 0; - instance->_showTimer.stop(); - instance->_hideByLeaveTimer.stop(); - instance->hide(); - instance->deleteLater(); - } -} diff --git a/Telegram/SourceFiles/ui/popupmenu.h b/Telegram/SourceFiles/ui/popupmenu.h deleted file mode 100644 index 4413ec543..000000000 --- a/Telegram/SourceFiles/ui/popupmenu.h +++ /dev/null @@ -1,158 +0,0 @@ -/* - This file is part of Telegram Desktop, - the official desktop version of Telegram messaging app, see https://telegram.org - - Telegram Desktop is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - It is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE - Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org - */ -#pragma once - -#include "ui/text/text.h" -#include "ui/effects/rect_shadow.h" - -class PopupMenu : public TWidget { - Q_OBJECT - -public: - PopupMenu(const style::PopupMenu &st = st::defaultPopupMenu); - PopupMenu(QMenu *menu, const style::PopupMenu &st = st::defaultPopupMenu); - QAction *addAction(const QString &text, const QObject *receiver, const char* member); - QAction *addAction(QAction *a); - QAction *addSeparator(); - void resetActions(); - - typedef QVector Actions; - Actions &actions(); - - void deleteOnHide(bool del); - void popup(const QPoint &p); - void hideMenu(bool fast = false); - - ~PopupMenu(); - -protected: - void resizeEvent(QResizeEvent *e) override; - void paintEvent(QPaintEvent *e) override; - void keyPressEvent(QKeyEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - void mousePressEvent(QMouseEvent *e) override; - void leaveEvent(QEvent *e) override; - void enterEvent(QEvent *e) override; - void focusOutEvent(QFocusEvent *e) override; - void hideEvent(QHideEvent *e) override; - -private slots: - void actionChanged(); - -private: - void updateSelected(); - - void childHiding(PopupMenu *child); - - void step_hide(float64 ms, bool timer); - - void init(); - void hideFinish(); - - enum PressSource { - PressSourceMouse, - PressSourceKeyboard, - }; - - void clearActions(bool force = false); - int32 processAction(QAction *a, int32 index, int32 w); - void setSelected(int32 selected); - int32 itemY(int32 index); - void updateSelectedItem(); - void itemPressed(PressSource source); - void popupChildMenu(PressSource source); - void showMenu(const QPoint &p, PopupMenu *parent, PressSource source);; - - const style::PopupMenu &_st; - - typedef QVector PopupMenus; - - QMenu *_menu = nullptr; - Actions _actions; - PopupMenus _menus; - PopupMenu *_parent = nullptr; - QStringList _texts, _shortcutTexts; - - int32 _itemHeight, _separatorHeight; - QRect _inner; - style::margins _padding; - - QPoint _mouse; - bool _mouseSelection = false; - - Ui::RectShadow _shadow; - int _selected = -1; - int _childMenuIndex = -1; - - QPixmap _cache; - anim::fvalue a_opacity; - Animation _a_hide; - - bool _deleteOnHide = true; - bool _triggering = false; - bool _deleteLater = false; - -}; - -class AbstractTooltipShower { -public: - virtual QString tooltipText() const = 0; - virtual QPoint tooltipPos() const = 0; - virtual const style::Tooltip *tooltipSt() const { - return &st::defaultTooltip; - } - virtual ~AbstractTooltipShower(); -}; - -class PopupTooltip : public TWidget { - Q_OBJECT - -public: - bool eventFilter(QObject *o, QEvent *e); - - static void Show(int32 delay, const AbstractTooltipShower *shower); - static void Hide(); - - ~PopupTooltip(); - -public slots: - void onShow(); - void onWndActiveChanged(); - void onHideByLeave(); - -protected: - void paintEvent(QPaintEvent *e); - void hideEvent(QHideEvent *e); - -private: - PopupTooltip(); - - void popup(const QPoint &p, const QString &text, const style::Tooltip *st); - - friend class AbstractTooltipShower; - const AbstractTooltipShower *_shower; - QTimer _showTimer; - - Text _text; - QPoint _point; - - const style::Tooltip *_st; - - QTimer _hideByLeaveTimer; - -}; diff --git a/Telegram/SourceFiles/ui/scrollarea.h b/Telegram/SourceFiles/ui/scrollarea.h index 98a79881c..12c80c027 100644 --- a/Telegram/SourceFiles/ui/scrollarea.h +++ b/Telegram/SourceFiles/ui/scrollarea.h @@ -273,19 +273,3 @@ public: } void paintEvent(QPaintEvent *e); }; - -class ScrolledWidget : public TWidget { - Q_OBJECT - -public: - ScrolledWidget(QWidget *parent = nullptr) : TWidget(parent) { - } - - // Updates the area that is visible inside the scroll container. - virtual void setVisibleTopBottom(int visibleTop, int visibleBottom) { - } - -signals: - void heightUpdated(); - -}; diff --git a/Telegram/SourceFiles/ui/twidget.h b/Telegram/SourceFiles/ui/twidget.h index 07ca80262..8c9c6f964 100644 --- a/Telegram/SourceFiles/ui/twidget.h +++ b/Telegram/SourceFiles/ui/twidget.h @@ -228,6 +228,14 @@ public: } } + // Updates the area that is visible inside the scroll container. + virtual void setVisibleTopBottom(int visibleTop, int visibleBottom) { + } + +signals: + // Child widget is responsible for emitting this signal. + void heightUpdated(); + protected: void enterEventHook(QEvent *e) { return QWidget::enterEvent(e); diff --git a/Telegram/SourceFiles/ui/widgets/dropdown_menu.cpp b/Telegram/SourceFiles/ui/widgets/dropdown_menu.cpp new file mode 100644 index 000000000..77ecdbf0a --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/dropdown_menu.cpp @@ -0,0 +1,270 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "ui/widgets/dropdown_menu.h" + +#include "application.h" +#include "lang.h" + +namespace Ui { + +DropdownMenu::DropdownMenu(QWidget *parent, const style::DropdownMenu &st) : InnerDropdown(parent, st.wrap) +, _st(st) +, _menu(this, _st.menu) { + init(); +} + +// Not ready with submenus yet. +//DropdownMenu::DropdownMenu(QWidget *parent, QMenu *menu, const style::DropdownMenu &st) : InnerDropdown(parent, st.wrap) +//, _st(st) +//, _menu(this, menu, _st.menu) { +// init(); +// +// for (auto action : actions()) { +// if (auto submenu = action->menu()) { +// auto it = _submenus.insert(action, new DropdownMenu(submenu, st)); +// it.value()->deleteOnHide(false); +// } +// } +//} + +void DropdownMenu::init() { + connect(this, SIGNAL(beforeHidden()), this, SLOT(onHidden())); + + setOwnedWidget(_menu); + + _menu->setResizedCallback([this] { resizeToContent(); }); + _menu->setActivatedCallback([this](QAction *action, int actionTop, TriggeredSource source) { + handleActivated(action, actionTop, source); + }); + _menu->setTriggeredCallback([this](QAction *action, int actionTop, TriggeredSource source) { + handleTriggered(action, actionTop, source); + }); + _menu->setKeyPressDelegate([this](int key) { return handleKeyPress(key); }); + _menu->setMouseMoveDelegate([this](QPoint globalPosition) { handleMouseMove(globalPosition); }); + _menu->setMousePressDelegate([this](QPoint globalPosition) { handleMousePress(globalPosition); }); + + setMouseTracking(true); + + hide(); +} + +QAction *DropdownMenu::addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon) { + return _menu->addAction(text, receiver, member, icon); +} + +QAction *DropdownMenu::addSeparator() { + return _menu->addSeparator(); +} + +void DropdownMenu::clearActions() { + //for (auto submenu : base::take(_submenus)) { + // delete submenu; + //} + return _menu->clearActions(); +} + +DropdownMenu::Actions &DropdownMenu::actions() { + return _menu->actions(); +} + +void DropdownMenu::handleActivated(QAction *action, int actionTop, TriggeredSource source) { + if (source == TriggeredSource::Mouse) { + if (!popupSubmenuFromAction(action, actionTop, source)) { + if (auto currentSubmenu = base::take(_activeSubmenu)) { + currentSubmenu->hideMenu(true); + } + } + } +} + +void DropdownMenu::handleTriggered(QAction *action, int actionTop, TriggeredSource source) { + if (!popupSubmenuFromAction(action, actionTop, source)) { + hideMenu(); + _triggering = true; + emit action->trigger(); + _triggering = false; + if (_deleteLater) { + _deleteLater = false; + deleteLater(); + } + } +} + +// Not ready with submenus yet. +bool DropdownMenu::popupSubmenuFromAction(QAction *action, int actionTop, TriggeredSource source) { + //if (auto submenu = _submenus.value(action)) { + // if (_activeSubmenu == submenu) { + // submenu->hideMenu(true); + // } else { + // popupSubmenu(submenu, actionTop, source); + // } + // return true; + //} + return false; +} + +//void DropdownMenu::popupSubmenu(SubmenuPointer submenu, int actionTop, TriggeredSource source) { +// if (auto currentSubmenu = base::take(_activeSubmenu)) { +// currentSubmenu->hideMenu(true); +// } +// if (submenu) { +// auto menuTopLeft = mapFromGlobal(_menu->mapToGlobal(QPoint(0, 0))); +// auto menuBottomRight = mapFromGlobal(_menu->mapToGlobal(QPoint(_menu->width(), _menu->height()))); +// QPoint p(menuTopLeft.x() + (rtl() ? (width() - menuBottomRight.x()) : menuBottomRight.x()), menuTopLeft.y() + actionTop); +// _activeSubmenu = submenu; +// _activeSubmenu->showMenu(geometry().topLeft() + p, this, source); +// +// _menu->setChildShown(true); +// } else { +// _menu->setChildShown(false); +// } +//} + +void DropdownMenu::forwardKeyPress(int key) { + if (!handleKeyPress(key)) { + _menu->handleKeyPress(key); + } +} + +bool DropdownMenu::handleKeyPress(int key) { + if (_activeSubmenu) { + _activeSubmenu->handleKeyPress(key); + return true; + } else if (key == Qt::Key_Escape) { + hideMenu(_parent ? true : false); + return true; + } else if (key == (rtl() ? Qt::Key_Right : Qt::Key_Left)) { + if (_parent) { + hideMenu(true); + return true; + } + } + return false; +} + +void DropdownMenu::handleMouseMove(QPoint globalPosition) { + if (_parent) { + _parent->forwardMouseMove(globalPosition); + } +} + +void DropdownMenu::handleMousePress(QPoint globalPosition) { + if (_parent) { + _parent->forwardMousePress(globalPosition); + } else { + hideMenu(); + } +} + +void DropdownMenu::focusOutEvent(QFocusEvent *e) { + hideMenu(); +} + +void DropdownMenu::hideEvent(QHideEvent *e) { + if (_deleteOnHide) { + if (_triggering) { + _deleteLater = true; + } else { + deleteLater(); + } + } +} + +void DropdownMenu::hideMenu(bool fast) { + if (isHidden()) return; + if (_parent && !isHiding()) { + _parent->childHiding(this); + } + if (fast) { + hideFast(); + } else { + hideAnimated(); + if (_parent) { + _parent->hideMenu(); + } + } + if (_activeSubmenu) { + _activeSubmenu->hideMenu(fast); + } +} + +void DropdownMenu::childHiding(DropdownMenu *child) { + if (_activeSubmenu && _activeSubmenu == child) { + _activeSubmenu = SubmenuPointer(); + } +} + +void DropdownMenu::hideFinish() { + _menu->clearSelection(); +} + +// Not ready with submenus yet. +//void DropdownMenu::deleteOnHide(bool del) { +// _deleteOnHide = del; +//} + +//void DropdownMenu::popup(const QPoint &p) { +// showMenu(p, nullptr, TriggeredSource::Mouse); +//} +// +//void DropdownMenu::showMenu(const QPoint &p, DropdownMenu *parent, TriggeredSource source) { +// _parent = parent; +// +// auto menuTopLeft = mapFromGlobal(_menu->mapToGlobal(QPoint(0, 0))); +// auto w = p - QPoint(0, menuTopLeft.y()); +// auto r = Sandbox::screenGeometry(p); +// if (rtl()) { +// if (w.x() - width() < r.x() - _padding.left()) { +// if (_parent && w.x() + _parent->width() - _padding.left() - _padding.right() + width() - _padding.right() <= r.x() + r.width()) { +// w.setX(w.x() + _parent->width() - _padding.left() - _padding.right()); +// } else { +// w.setX(r.x() - _padding.left()); +// } +// } else { +// w.setX(w.x() - width()); +// } +// } else { +// if (w.x() + width() - _padding.right() > r.x() + r.width()) { +// if (_parent && w.x() - _parent->width() + _padding.left() + _padding.right() - width() + _padding.right() >= r.x() - _padding.left()) { +// w.setX(w.x() + _padding.left() + _padding.right() - _parent->width() - width() + _padding.left() + _padding.right()); +// } else { +// w.setX(r.x() + r.width() - width() + _padding.right()); +// } +// } +// } +// if (w.y() + height() - _padding.bottom() > r.y() + r.height()) { +// if (_parent) { +// w.setY(r.y() + r.height() - height() + _padding.bottom()); +// } else { +// w.setY(p.y() - height() + _padding.bottom()); +// } +// } +// if (w.y() < r.y()) { +// w.setY(r.y()); +// } +// move(w); +// +// _menu->setShowSource(source); +//} + +DropdownMenu::~DropdownMenu() { + clearActions(); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/dropdown_menu.h b/Telegram/SourceFiles/ui/widgets/dropdown_menu.h new file mode 100644 index 000000000..e88c33e59 --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/dropdown_menu.h @@ -0,0 +1,110 @@ +/* + This file is part of Telegram Desktop, + the official desktop version of Telegram messaging app, see https://telegram.org + + Telegram Desktop is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + It is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE + Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org + */ +#pragma once + +#include "styles/style_widgets.h" +#include "ui/effects/rect_shadow.h" +#include "ui/widgets/inner_dropdown.h" +#include "ui/widgets/menu.h" + +namespace Ui { + +class DropdownMenu : public InnerDropdown { + Q_OBJECT + +public: + DropdownMenu(QWidget *parent, const style::DropdownMenu &st = st::defaultDropdownMenu); + + QAction *addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon = nullptr); + QAction *addSeparator(); + void clearActions(); + + using Actions = Ui::Menu::Actions; + Actions &actions(); + + ~DropdownMenu(); + +protected: + void focusOutEvent(QFocusEvent *e) override; + void hideEvent(QHideEvent *e) override; + + void keyPressEvent(QKeyEvent *e) override { + forwardKeyPress(e->key()); + } + void mouseMoveEvent(QMouseEvent *e) override { + forwardMouseMove(e->globalPos()); + } + void mousePressEvent(QMouseEvent *e) override { + forwardMousePress(e->globalPos()); + } + +private slots: + void onHidden() { + hideFinish(); + } + +private: + // Not ready with submenus yet. + DropdownMenu(QWidget *parent, QMenu *menu, const style::DropdownMenu &st = st::defaultDropdownMenu); + void deleteOnHide(bool del); + void popup(const QPoint &p); + void hideMenu(bool fast = false); + + void childHiding(DropdownMenu *child); + + void init(); + void hideFinish(); + + using TriggeredSource = Ui::Menu::TriggeredSource; + void handleActivated(QAction *action, int actionTop, TriggeredSource source); + void handleTriggered(QAction *action, int actionTop, TriggeredSource source); + void forwardKeyPress(int key); + bool handleKeyPress(int key); + void forwardMouseMove(QPoint globalPosition) { + _menu->handleMouseMove(globalPosition); + } + void handleMouseMove(QPoint globalPosition); + void forwardMousePress(QPoint globalPosition) { + _menu->handleMousePress(globalPosition); + } + void handleMousePress(QPoint globalPosition); + + using SubmenuPointer = QPointer; + bool popupSubmenuFromAction(QAction *action, int actionTop, TriggeredSource source); + void popupSubmenu(SubmenuPointer submenu, int actionTop, TriggeredSource source); + void showMenu(const QPoint &p, DropdownMenu *parent, TriggeredSource source); + + const style::DropdownMenu &_st; + + ChildWidget _menu; + + // Not ready with submenus yet. + //using Submenus = QMap; + //Submenus _submenus; + + DropdownMenu *_parent = nullptr; + + SubmenuPointer _activeSubmenu; + + bool _deleteOnHide = false; + bool _triggering = false; + bool _deleteLater = false; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/inner_dropdown.cpp b/Telegram/SourceFiles/ui/widgets/inner_dropdown.cpp similarity index 60% rename from Telegram/SourceFiles/ui/inner_dropdown.cpp rename to Telegram/SourceFiles/ui/widgets/inner_dropdown.cpp index 8065c18e0..d7f5cd622 100644 --- a/Telegram/SourceFiles/ui/inner_dropdown.cpp +++ b/Telegram/SourceFiles/ui/widgets/inner_dropdown.cpp @@ -19,7 +19,7 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "ui/inner_dropdown.h" +#include "ui/widgets/inner_dropdown.h" #include "mainwindow.h" #include "ui/scrollarea.h" @@ -27,12 +27,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Ui { -InnerDropdown::InnerDropdown(QWidget *parent, const style::InnerDropdown &st, const style::flatScroll &scrollSt) : TWidget(parent) +InnerDropdown::InnerDropdown(QWidget *parent, const style::InnerDropdown &st) : TWidget(parent) , _st(st) , _shadow(_st.shadow) -, _scroll(this, scrollSt) { +, _scroll(this, _st.scroll) { _hideTimer.setSingleShot(true); - connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(onHideStart())); + connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(onHideAnimated())); connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); @@ -43,9 +43,9 @@ InnerDropdown::InnerDropdown(QWidget *parent, const style::InnerDropdown &st, co hide(); } -void InnerDropdown::setOwnedWidget(ScrolledWidget *widget) { - auto container = new internal::Container(_scroll, widget, _st); - connect(container, SIGNAL(heightUpdated()), this, SLOT(onWidgetHeightUpdated())); +void InnerDropdown::setOwnedWidget(TWidget *widget) { + auto container = new Container(_scroll, widget, _st); + connect(widget, SIGNAL(heightUpdated()), this, SLOT(onWidgetHeightUpdated())); _scroll->setOwnedWidget(container); container->resizeToWidth(_scroll->width()); container->moveToLeft(0, 0); @@ -55,23 +55,22 @@ void InnerDropdown::setOwnedWidget(ScrolledWidget *widget) { void InnerDropdown::setMaxHeight(int newMaxHeight) { _maxHeight = newMaxHeight; - updateHeight(); + resizeToContent(); } -void InnerDropdown::onWidgetHeightUpdated() { - updateHeight(); -} - -void InnerDropdown::updateHeight() { - int newHeight = _st.padding.top() + _st.scrollMargin.top() + _st.scrollMargin.bottom() + _st.padding.bottom(); - if (auto widget = static_cast(_scroll->widget())) { +void InnerDropdown::resizeToContent() { + auto newWidth = _st.padding.left() + _st.scrollMargin.left() + _st.scrollMargin.right() + _st.padding.right(); + auto newHeight = _st.padding.top() + _st.scrollMargin.top() + _st.scrollMargin.bottom() + _st.padding.bottom(); + if (auto widget = static_cast(_scroll->widget())) { + widget->resizeToContent(); + newWidth += widget->width(); newHeight += widget->height(); } if (_maxHeight > 0) { accumulate_min(newHeight, _maxHeight); } - if (newHeight != height()) { - resize(width(), newHeight); + if (newWidth != width() || newHeight != height()) { + resize(newWidth, newHeight); } } @@ -83,14 +82,14 @@ void InnerDropdown::onWindowActiveChanged() { void InnerDropdown::resizeEvent(QResizeEvent *e) { _scroll->setGeometry(rect().marginsRemoved(_st.padding).marginsRemoved(_st.scrollMargin)); - if (auto widget = static_cast(_scroll->widget())) { + if (auto widget = static_cast(_scroll->widget())) { widget->resizeToWidth(_scroll->width()); onScroll(); } } void InnerDropdown::onScroll() { - if (auto widget = static_cast(_scroll->widget())) { + if (auto widget = static_cast(_scroll->widget())) { int visibleTop = _scroll->scrollTop(); int visibleBottom = visibleTop + _scroll->height(); widget->setVisibleTopBottom(visibleTop, visibleBottom); @@ -103,9 +102,9 @@ void InnerDropdown::paintEvent(QPaintEvent *e) { if (!_cache.isNull()) { bool animating = _a_appearance.animating(getms()); if (animating) { - p.setOpacity(_a_appearance.current(_hiding)); - } else if (_hiding) { - hidingFinished(); + p.setOpacity(_a_appearance.current(_hiding ? 0. : 1.)); + } else if (_hiding || isHidden()) { + hideFinished(); return; } p.drawPixmap(0, 0, _cache); @@ -117,20 +116,19 @@ void InnerDropdown::paintEvent(QPaintEvent *e) { } // draw shadow - QRect shadowedRect = rect().marginsRemoved(_st.padding); + auto shadowedRect = rect().marginsRemoved(_st.padding); _shadow.paint(p, shadowedRect, _st.shadowShift); p.fillRect(shadowedRect, st::windowBg); } void InnerDropdown::enterEvent(QEvent *e) { - _hideTimer.stop(); - if (_hiding) showingStarted(); + showAnimated(); return TWidget::enterEvent(e); } void InnerDropdown::leaveEvent(QEvent *e) { if (_a_appearance.animating(getms())) { - onHideStart(); + hideAnimated(); } else { _hideTimer.start(300); } @@ -138,25 +136,43 @@ void InnerDropdown::leaveEvent(QEvent *e) { } void InnerDropdown::otherEnter() { - _hideTimer.stop(); - showingStarted(); + showAnimated(); } void InnerDropdown::otherLeave() { if (_a_appearance.animating(getms())) { - onHideStart(); + hideAnimated(); } else { _hideTimer.start(0); } } -void InnerDropdown::onHideStart() { +void InnerDropdown::showAnimated() { + _hideTimer.stop(); + showStarted(); +} + +void InnerDropdown::hideAnimated(HideOption option) { + if (isHidden()) return; + if (option == HideOption::IgnoreShow) { + _ignoreShowEvents = true; + } if (_hiding) return; + _hideTimer.stop(); _hiding = true; startAnimation(); } +void InnerDropdown::hideFast() { + if (isHidden()) return; + + _hideTimer.stop(); + _hiding = false; + _a_appearance.finish(); + hideFinished(); +} + void InnerDropdown::startAnimation() { auto from = _hiding ? 1. : 0.; auto to = _hiding ? 0. : 1.; @@ -168,13 +184,17 @@ void InnerDropdown::startAnimation() { _a_appearance.start([this] { repaintCallback(); }, from, to, _st.duration); } -void InnerDropdown::hidingFinished() { - hide(); -// showChildren(); - emit hidden(); +void InnerDropdown::hideFinished() { + _cache = QPixmap(); + _ignoreShowEvents = false; + if (!isHidden()) { + emit beforeHidden(); + hide(); + } } -void InnerDropdown::showingStarted() { +void InnerDropdown::showStarted() { + if (_ignoreShowEvents) return; if (isHidden()) { show(); } else if (!_hiding) { @@ -188,7 +208,7 @@ void InnerDropdown::repaintCallback() { update(); if (!_a_appearance.animating() && _hiding) { _hiding = false; - hidingFinished(); + hideFinished(); } } @@ -207,35 +227,45 @@ bool InnerDropdown::eventFilter(QObject *obj, QEvent *e) { return false; } -namespace internal { - -Container::Container(QWidget *parent, ScrolledWidget *child, const style::InnerDropdown &st) : ScrolledWidget(parent), _st(st) { - child->setParent(this); - child->moveToLeft(_st.scrollPadding.left(), _st.scrollPadding.top()); - connect(child, SIGNAL(heightUpdated()), this, SLOT(onHeightUpdate())); +int InnerDropdown::resizeGetHeight(int newWidth) { + auto newHeight = _st.padding.top() + _st.scrollMargin.top() + _st.scrollMargin.bottom() + _st.padding.bottom(); + if (auto widget = static_cast(_scroll->widget())) { + widget->resizeToWidth(newWidth - _st.padding.left() - _st.padding.right() - _st.scrollMargin.left() - _st.scrollMargin.right()); + newHeight += widget->height(); + } + if (_maxHeight > 0) { + accumulate_min(newHeight, _maxHeight); + } + return newHeight; } -void Container::setVisibleTopBottom(int visibleTop, int visibleBottom) { - if (auto child = static_cast(children().front())) { +InnerDropdown::Container::Container(QWidget *parent, TWidget *child, const style::InnerDropdown &st) : TWidget(parent), _st(st) { + child->setParent(this); + child->moveToLeft(_st.scrollPadding.left(), _st.scrollPadding.top()); +} + +void InnerDropdown::Container::setVisibleTopBottom(int visibleTop, int visibleBottom) { + if (auto child = static_cast(children().front())) { child->setVisibleTopBottom(visibleTop - _st.scrollPadding.top(), visibleBottom - _st.scrollPadding.top()); } } -void Container::onHeightUpdate() { - int newHeight = _st.scrollPadding.top() + _st.scrollPadding.bottom(); - if (auto child = static_cast(children().front())) { +void InnerDropdown::Container::resizeToContent() { + auto newWidth = _st.scrollPadding.top() + _st.scrollPadding.bottom(); + auto newHeight = _st.scrollPadding.top() + _st.scrollPadding.bottom(); + if (auto child = static_cast(children().front())) { + newWidth += child->width(); newHeight += child->height(); } - if (newHeight != height()) { - resize(width(), newHeight); - emit heightUpdated(); + if (newWidth != width() || newHeight != height()) { + resize(newWidth, newHeight); } } -int Container::resizeGetHeight(int newWidth) { +int InnerDropdown::Container::resizeGetHeight(int newWidth) { int innerWidth = newWidth - _st.scrollPadding.left() - _st.scrollPadding.right(); int result = _st.scrollPadding.top() + _st.scrollPadding.bottom(); - if (auto child = static_cast(children().front())) { + if (auto child = static_cast(children().front())) { child->resizeToWidth(innerWidth); child->moveToLeft(_st.scrollPadding.left(), _st.scrollPadding.top()); result += child->height(); @@ -243,5 +273,4 @@ int Container::resizeGetHeight(int newWidth) { return result; } -} // namespace internal } // namespace Ui diff --git a/Telegram/SourceFiles/ui/inner_dropdown.h b/Telegram/SourceFiles/ui/widgets/inner_dropdown.h similarity index 73% rename from Telegram/SourceFiles/ui/inner_dropdown.h rename to Telegram/SourceFiles/ui/widgets/inner_dropdown.h index 2115679a4..9b251042a 100644 --- a/Telegram/SourceFiles/ui/inner_dropdown.h +++ b/Telegram/SourceFiles/ui/widgets/inner_dropdown.h @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once #include "ui/effects/rect_shadow.h" +#include "styles/style_widgets.h" class ScrollArea; @@ -30,9 +31,9 @@ class InnerDropdown : public TWidget { Q_OBJECT public: - InnerDropdown(QWidget *parent, const style::InnerDropdown &st = st::defaultInnerDropdown, const style::flatScroll &scrollSt = st::scrollDef); + InnerDropdown(QWidget *parent, const style::InnerDropdown &st = st::defaultInnerDropdown); - void setOwnedWidget(ScrolledWidget *widget); + void setOwnedWidget(TWidget *widget); bool overlaps(const QRect &globalRect) { if (isHidden() || _a_appearance.animating()) return false; @@ -41,32 +42,52 @@ public: } void setMaxHeight(int newMaxHeight); + void resizeToContent(); void otherEnter(); void otherLeave(); + void showFast(); + void hideFast(); + + bool isHiding() const { + return _hiding && _a_appearance.animating(); + } + + void showAnimated(); + enum class HideOption { + Default, + IgnoreShow, + }; + void hideAnimated(HideOption option = HideOption::Default); + +signals: + void beforeHidden(); protected: void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; void enterEvent(QEvent *e) override; void leaveEvent(QEvent *e) override; - bool eventFilter(QObject *obj, QEvent *e) override; -signals: - void hidden(); + int resizeGetHeight(int newWidth) override; private slots: - void onHideStart(); + void onHideAnimated() { + hideAnimated(); + } void onWindowActiveChanged(); void onScroll(); - void onWidgetHeightUpdated(); + void onWidgetHeightUpdated() { + resizeToContent(); + } private: + class Container; void repaintCallback(); - void hidingFinished(); - void showingStarted(); + void hideFinished(); + void showStarted(); void startAnimation(); @@ -80,6 +101,7 @@ private: FloatAnimation _a_appearance; QTimer _hideTimer; + bool _ignoreShowEvents = false; RectShadow _shadow; ChildWidget _scroll; @@ -88,17 +110,12 @@ private: }; -namespace internal { - -class Container : public ScrolledWidget { - Q_OBJECT - +class InnerDropdown::Container : public TWidget { public: - Container(QWidget *parent, ScrolledWidget *child, const style::InnerDropdown &st); + Container(QWidget *parent, TWidget *child, const style::InnerDropdown &st); void setVisibleTopBottom(int visibleTop, int visibleBottom) override; -private slots: - void onHeightUpdate(); + void resizeToContent(); protected: int resizeGetHeight(int newWidth) override; @@ -108,5 +125,4 @@ private: }; -} // namespace internal } // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/menu.cpp b/Telegram/SourceFiles/ui/widgets/menu.cpp new file mode 100644 index 000000000..46b374dd1 --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/menu.cpp @@ -0,0 +1,343 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "ui/widgets/menu.h" + +namespace Ui { + +Menu::Menu(QWidget *parent, const style::Menu &st) : TWidget(parent) +, _st(st) +, _itemHeight(_st.itemPadding.top() + _st.itemFont->height + _st.itemPadding.bottom()) +, _separatorHeight(_st.separatorPadding.top() + _st.separatorWidth + _st.separatorPadding.bottom()) { + init(); +} + +Menu::Menu(QWidget *parent, QMenu *menu, const style::Menu &st) : TWidget(parent) +, _st(st) +, _wappedMenu(menu) +, _itemHeight(_st.itemPadding.top() + _st.itemFont->height + _st.itemPadding.bottom()) +, _separatorHeight(_st.separatorPadding.top() + _st.separatorWidth + _st.separatorPadding.bottom()) { + init(); + + _wappedMenu->setParent(this); + for (auto action : _wappedMenu->actions()) { + addAction(action); + } + _wappedMenu->hide(); +} + +void Menu::init() { + resize(_st.widthMin, _st.skip * 2); + + setMouseTracking(true); + + setAttribute(Qt::WA_OpaquePaintEvent); +} + +QAction *Menu::addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon) { + auto action = new QAction(text, this); + connect(action, SIGNAL(triggered(bool)), receiver, member, Qt::QueuedConnection); + return addAction(action, icon); +} + +QAction *Menu::addAction(QAction *action, const style::icon *icon) { + connect(action, SIGNAL(changed()), this, SLOT(actionChanged())); + _actions.push_back(action); + + ActionData data; + data.icon = icon; + data.hasSubmenu = (action->menu() != nullptr); + _actionsData.push_back(data); + + auto newWidth = qMax(width(), _st.widthMin); + newWidth = processAction(action, _actions.size() - 1, newWidth); + auto newHeight = height() + (action->isSeparator() ? _separatorHeight : _itemHeight); + resize(newWidth, newHeight); + if (_resizedCallback) { + _resizedCallback(); + } + update(); + + return action; +} + +QAction *Menu::addSeparator() { + auto separator = new QAction(this); + separator->setSeparator(true); + return addAction(separator); +} + +void Menu::clearActions() { + _actionsData.clear(); + for (auto action : base::take(_actions)) { + if (action->parent() == this) { + delete action; + } + } + resize(_st.widthMin, _st.skip * 2); + if (_resizedCallback) { + _resizedCallback(); + } +} + +int Menu::processAction(QAction *action, int index, int width) { + auto &data = _actionsData[index]; + if (action->isSeparator() || action->text().isEmpty()) { + data.text = data.shortcut = QString(); + } else { + auto actionTextParts = action->text().split('\t'); + auto actionText = actionTextParts.empty() ? QString() : actionTextParts[0]; + auto actionShortcut = (actionTextParts.size() > 1) ? actionTextParts[1] : QString(); + int textw = _st.itemFont->width(actionText); + int goodw = _st.itemPadding.left() + textw + _st.itemPadding.right(); + if (data.hasSubmenu) { + goodw += _st.itemPadding.left() + _st.arrow.width(); + } else if (!actionShortcut.isEmpty()) { + goodw += _st.itemPadding.left() + _st.itemFont->width(actionShortcut); + } + width = snap(goodw, width, _st.widthMax); + data.text = (width < goodw) ? _st.itemFont->elided(actionText, width - (goodw - textw)) : actionText; + data.shortcut = actionShortcut; + } + return width; +} + +void Menu::setShowSource(TriggeredSource source) { + _mouseSelection = (source == TriggeredSource::Mouse); + setSelected((source == TriggeredSource::Mouse || _actions.isEmpty()) ? -1 : 0); +} + +Menu::Actions &Menu::actions() { + return _actions; +} + +void Menu::actionChanged() { + int newWidth = _st.widthMin; + for (int i = 0, count = _actions.size(); i != count; ++i) { + newWidth = processAction(_actions[i], i, newWidth); + } + if (newWidth != width()) { + resize(newWidth, height()); + if (_resizedCallback) { + _resizedCallback(); + } + } + update(); +} + +void Menu::paintEvent(QPaintEvent *e) { + Painter p(this); + + auto clip = e->rect(); + + auto topskip = QRect(0, 0, width(), _st.skip); + auto bottomskip = QRect(0, height() - _st.skip, width(), _st.skip); + if (clip.intersects(topskip)) p.fillRect(clip.intersected(topskip), _st.itemBg); + if (clip.intersects(bottomskip)) p.fillRect(clip.intersected(bottomskip), _st.itemBg); + + int top = _st.skip; + p.translate(0, top); + p.setFont(_st.itemFont); + for (int i = 0, count = _actions.size(); i != count; ++i) { + if (clip.top() + clip.height() <= top) break; + + auto action = _actions[i]; + auto &data = _actionsData[i]; + auto actionHeight = action->isSeparator() ? _separatorHeight : _itemHeight; + top += actionHeight; + if (clip.top() < top) { + if (action->isSeparator()) { + p.fillRect(0, 0, width(), actionHeight, _st.itemBg); + p.fillRect(_st.separatorPadding.left(), _st.separatorPadding.top(), width() - _st.separatorPadding.left() - _st.separatorPadding.right(), _st.separatorWidth, _st.separatorFg); + } else { + auto enabled = action->isEnabled(), selected = (i == _selected && enabled); + p.fillRect(0, 0, width(), actionHeight, selected ? _st.itemBgOver : _st.itemBg); + if (data.icon) { + p.setOpacity(selected ? _st.itemIconOverOpacity : _st.itemIconOpacity); + data.icon->paint(p, _st.itemIconPosition, width()); + p.setOpacity(1.); + } + p.setPen(selected ? _st.itemFgOver : (enabled ? _st.itemFg : _st.itemFgDisabled)); + p.drawTextLeft(_st.itemPadding.left(), _st.itemPadding.top(), width(), data.text); + if (data.hasSubmenu) { + _st.arrow.paint(p, width() - _st.itemPadding.right() - _st.arrow.width(), (_itemHeight - _st.arrow.height()) / 2, width()); + } else if (!data.shortcut.isEmpty()) { + p.setPen(selected ? _st.itemFgShortcutOver : (enabled ? _st.itemFgShortcut : _st.itemFgShortcutDisabled)); + p.drawTextRight(_st.itemPadding.right(), _st.itemPadding.top(), width(), data.shortcut); + } + } + } + p.translate(0, actionHeight); + } +} + +void Menu::updateSelected(QPoint globalPosition) { + if (!_mouseSelection) return; + + auto p = mapFromGlobal(globalPosition) - QPoint(0, _st.skip); + auto selected = -1, top = 0; + while (top <= p.y() && ++selected < _actions.size()) { + top += _actions[selected]->isSeparator() ? _separatorHeight : _itemHeight; + } + setSelected((selected >= 0 && selected < _actions.size() && _actions[selected]->isEnabled() && !_actions[selected]->isSeparator()) ? selected : -1); +} + +void Menu::itemPressed(TriggeredSource source) { + if (source == TriggeredSource::Mouse && !_mouseSelection) { + return; + } + if (_selected >= 0 && _selected < _actions.size() && _actions[_selected]->isEnabled()) { + if (_triggeredCallback) { + auto actionTop = _st.skip + itemTop(_selected); + _triggeredCallback(_actions[_selected], actionTop, source); + } + } +} + +void Menu::keyPressEvent(QKeyEvent *e) { + auto key = e->key(); + if (!_keyPressDelegate || !_keyPressDelegate(key)) { + handleKeyPress(key); + } +} + +void Menu::handleKeyPress(int key) { + if (key == Qt::Key_Enter || key == Qt::Key_Return) { + itemPressed(TriggeredSource::Keyboard); + return; + } + if (key == (rtl() ? Qt::Key_Left : Qt::Key_Right)) { + if (_selected >= 0 && _actionsData[_selected].hasSubmenu) { + itemPressed(TriggeredSource::Keyboard); + return; + } else if (_selected < 0 && !_actions.isEmpty()) { + _mouseSelection = false; + setSelected(0); + } + } + if ((key != Qt::Key_Up && key != Qt::Key_Down) || _actions.size() < 1) return; + + auto delta = (key == Qt::Key_Down ? 1 : -1), start = _selected; + if (start < 0 || start >= _actions.size()) { + start = (delta > 0) ? (_actions.size() - 1) : 0; + } + auto newSelected = start; + do { + newSelected += delta; + if (newSelected < 0) { + newSelected += _actions.size(); + } else if (newSelected >= _actions.size()) { + newSelected -= _actions.size(); + } + } while (newSelected != start && (!_actions.at(newSelected)->isEnabled() || _actions.at(newSelected)->isSeparator())); + + if (_actions.at(newSelected)->isEnabled() && !_actions.at(newSelected)->isSeparator()) { + _mouseSelection = false; + setSelected(newSelected); + } +} + +void Menu::clearSelection() { + _mouseSelection = false; + setSelected(-1); +} + +void Menu::clearMouseSelection() { + if (_mouseSelection && !_childShown) { + clearSelection(); + } +} + +void Menu::enterEvent(QEvent *e) { + QPoint mouse = QCursor::pos(); + if (!rect().marginsRemoved(QMargins(0, _st.skip, 0, _st.skip)).contains(mapFromGlobal(mouse))) { + clearMouseSelection(); + } + return TWidget::enterEvent(e); +} + +void Menu::leaveEvent(QEvent *e) { + clearMouseSelection(); + return TWidget::leaveEvent(e); +} + +void Menu::setSelected(int selected) { + if (selected >= _actions.size()) { + selected = -1; + } + if (_selected != selected) { + updateSelectedItem(); + _selected = selected; + updateSelectedItem(); + if (_activatedCallback) { + auto actionTop = _st.skip + itemTop(_selected); + auto source = _mouseSelection ? TriggeredSource::Mouse : TriggeredSource::Keyboard; + _activatedCallback((_selected >= 0) ? _actions[_selected] : nullptr, actionTop, source); + } + } +} + +int Menu::itemTop(int index) { + if (index > _actions.size()) { + index = _actions.size(); + } + int top = 0; + for (int i = 0; i < index; ++i) { + top += _actions.at(i)->isSeparator() ? _separatorHeight : _itemHeight; + } + return top; +} + +void Menu::updateSelectedItem() { + if (_selected >= 0) { + update(0, _st.skip + itemTop(_selected), width(), _actions.at(_selected)->isSeparator() ? _separatorHeight : _itemHeight); + } +} + +void Menu::mouseMoveEvent(QMouseEvent *e) { + handleMouseMove(e->globalPos()); +} + +void Menu::handleMouseMove(QPoint globalPosition) { + auto inner = rect().marginsRemoved(QMargins(0, _st.skip, 0, _st.skip)); + auto localPosition = mapFromGlobal(globalPosition); + if (inner.contains(localPosition)) { + _mouseSelection = true; + updateSelected(globalPosition); + } else { + clearMouseSelection(); + if (_mouseMoveDelegate) { + _mouseMoveDelegate(globalPosition); + } + } +} + +void Menu::mousePressEvent(QMouseEvent *e) { + handleMousePress(e->globalPos()); +} + +void Menu::handleMousePress(QPoint globalPosition) { + handleMouseMove(globalPosition); + if (rect().contains(mapFromGlobal(globalPosition))) { + itemPressed(TriggeredSource::Mouse); + } else if (_mousePressDelegate) { + _mousePressDelegate(globalPosition); + } +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/menu.h b/Telegram/SourceFiles/ui/widgets/menu.h new file mode 100644 index 000000000..64ecf8b11 --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/menu.h @@ -0,0 +1,134 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "styles/style_widgets.h" + +namespace Ui { + +class Menu : public TWidget { + Q_OBJECT + +public: + Menu(QWidget *parent, const style::Menu &st = st::defaultMenu); + Menu(QWidget *parent, QMenu *menu, const style::Menu &st = st::defaultMenu); + + QAction *addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon = nullptr); + QAction *addSeparator(); + void clearActions(); + + void clearSelection(); + + enum class TriggeredSource { + Mouse, + Keyboard, + }; + void setChildShown(bool shown) { + _childShown = shown; + } + void setShowSource(TriggeredSource source); + + using Actions = QList; + Actions &actions(); + + void setResizedCallback(base::lambda_unique callback) { + _resizedCallback = std_::move(callback); + } + + void setActivatedCallback(base::lambda_unique callback) { + _activatedCallback = std_::move(callback); + } + void setTriggeredCallback(base::lambda_unique callback) { + _triggeredCallback = std_::move(callback); + } + + void setKeyPressDelegate(base::lambda_unique delegate) { + _keyPressDelegate = std_::move(delegate); + } + void handleKeyPress(int key); + + void setMouseMoveDelegate(base::lambda_unique delegate) { + _mouseMoveDelegate = std_::move(delegate); + } + void handleMouseMove(QPoint globalPosition); + + void setMousePressDelegate(base::lambda_unique delegate) { + _mousePressDelegate = std_::move(delegate); + } + void handleMousePress(QPoint globalPosition); + +protected: + void paintEvent(QPaintEvent *e) override; + void keyPressEvent(QKeyEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void enterEvent(QEvent *e) override; + void leaveEvent(QEvent *e) override; + +private slots: + void actionChanged(); + +private: + void updateSelected(QPoint globalPosition); + void init(); + + // Returns the new width. + int processAction(QAction *action, int index, int width); + QAction *addAction(QAction *a, const style::icon *icon = nullptr); + + void setSelected(int selected); + void clearMouseSelection(); + + int itemTop(int index); + void updateSelectedItem(); + void itemPressed(TriggeredSource source); + + const style::Menu &_st; + + base::lambda_unique _resizedCallback; + base::lambda_unique _activatedCallback; + base::lambda_unique _triggeredCallback; + base::lambda_unique _keyPressDelegate; + base::lambda_unique _mouseMoveDelegate; + base::lambda_unique _mousePressDelegate; + + struct ActionData { + bool hasSubmenu = false; + QString text; + QString shortcut; + const style::icon *icon = nullptr; + }; + using ActionsData = QList; + + QMenu *_wappedMenu = nullptr; + Actions _actions; + ActionsData _actionsData; + + int _itemHeight, _separatorHeight; + + bool _mouseSelection = false; + + int _selected = -1; + bool _childShown = false; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/multi_select.cpp b/Telegram/SourceFiles/ui/widgets/multi_select.cpp index bef2de71b..d4bc4f68c 100644 --- a/Telegram/SourceFiles/ui/widgets/multi_select.cpp +++ b/Telegram/SourceFiles/ui/widgets/multi_select.cpp @@ -454,7 +454,7 @@ int MultiSelect::resizeGetHeight(int newWidth) { return newHeight; } -MultiSelect::Inner::Inner(QWidget *parent, const style::MultiSelect &st, const QString &placeholder, ScrollCallback callback) : ScrolledWidget(parent) +MultiSelect::Inner::Inner(QWidget *parent, const style::MultiSelect &st, const QString &placeholder, ScrollCallback callback) : TWidget(parent) , _st(st) , _scrollCallback(std_::move(callback)) , _field(this, _st.field, placeholder) diff --git a/Telegram/SourceFiles/ui/widgets/multi_select.h b/Telegram/SourceFiles/ui/widgets/multi_select.h index 01854db33..9ac568ee6 100644 --- a/Telegram/SourceFiles/ui/widgets/multi_select.h +++ b/Telegram/SourceFiles/ui/widgets/multi_select.h @@ -71,7 +71,7 @@ private: }; // This class is hold in header because it requires Qt preprocessing. -class MultiSelect::Inner : public ScrolledWidget { +class MultiSelect::Inner : public TWidget { Q_OBJECT public: diff --git a/Telegram/SourceFiles/ui/widgets/popup_menu.cpp b/Telegram/SourceFiles/ui/widgets/popup_menu.cpp new file mode 100644 index 000000000..685a90404 --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/popup_menu.cpp @@ -0,0 +1,340 @@ +/* + This file is part of Telegram Desktop, + the official desktop version of Telegram messaging app, see https://telegram.org + + Telegram Desktop is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + It is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE + Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org + */ +#include "stdafx.h" +#include "ui/widgets/popup_menu.h" + +#include "pspecific.h" +#include "application.h" +#include "lang.h" + +namespace Ui { + +PopupMenu::PopupMenu(const style::PopupMenu &st) : TWidget(nullptr) +, _st(st) +, _menu(this, _st.menu) +, _shadow(_st.shadow) +, a_opacity(1) +, _a_hide(animation(this, &PopupMenu::step_hide)) { + init(); +} + +PopupMenu::PopupMenu(QMenu *menu, const style::PopupMenu &st) : TWidget(nullptr) +, _st(st) +, _menu(this, menu, _st.menu) +, _shadow(_st.shadow) +, a_opacity(1) +, _a_hide(animation(this, &PopupMenu::step_hide)) { + init(); + + for (auto action : actions()) { + if (auto submenu = action->menu()) { + auto it = _submenus.insert(action, new PopupMenu(submenu, st)); + it.value()->deleteOnHide(false); + } + } +} + +void PopupMenu::init() { + _padding = _shadow.getDimensions(_st.shadowShift); + + _menu->setResizedCallback([this] { handleMenuResize(); }); + _menu->setActivatedCallback([this](QAction *action, int actionTop, TriggeredSource source) { + handleActivated(action, actionTop, source); + }); + _menu->setTriggeredCallback([this](QAction *action, int actionTop, TriggeredSource source) { + handleTriggered(action, actionTop, source); + }); + _menu->setKeyPressDelegate([this](int key) { return handleKeyPress(key); }); + _menu->setMouseMoveDelegate([this](QPoint globalPosition) { handleMouseMove(globalPosition); }); + _menu->setMousePressDelegate([this](QPoint globalPosition) { handleMousePress(globalPosition); }); + + _menu->moveToLeft(_padding.left(), _padding.top()); + handleMenuResize(); + + setWindowFlags(Qt::WindowFlags(Qt::FramelessWindowHint) | Qt::BypassWindowManagerHint | Qt::Popup | Qt::NoDropShadowWindowHint); + setMouseTracking(true); + + hide(); + + setAttribute(Qt::WA_NoSystemBackground, true); + setAttribute(Qt::WA_TranslucentBackground, true); +} + +void PopupMenu::handleMenuResize() { + resize(_padding.left() + _menu->width() + _padding.right(), _padding.top() + _menu->height() + _padding.bottom()); + _inner = QRect(_padding.left(), _padding.top(), width() - _padding.left() - _padding.right(), height() - _padding.top() - _padding.bottom()); +} + +QAction *PopupMenu::addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon) { + return _menu->addAction(text, receiver, member, icon); +} + +QAction *PopupMenu::addSeparator() { + return _menu->addSeparator(); +} + +void PopupMenu::clearActions() { + for (auto submenu : base::take(_submenus)) { + delete submenu; + } + return _menu->clearActions(); +} + +PopupMenu::Actions &PopupMenu::actions() { + return _menu->actions(); +} + +void PopupMenu::paintEvent(QPaintEvent *e) { + Painter p(this); + + auto clip = e->rect(); + p.setClipRect(clip); + auto compositionMode = p.compositionMode(); + p.setCompositionMode(QPainter::CompositionMode_Source); + if (_a_hide.animating()) { + p.setOpacity(a_opacity.current()); + p.drawPixmap(0, 0, _cache); + return; + } + + p.fillRect(clip, st::almostTransparent); + p.setCompositionMode(compositionMode); + + _shadow.paint(p, _inner, _st.shadowShift); +} + +void PopupMenu::handleActivated(QAction *action, int actionTop, TriggeredSource source) { + if (source == TriggeredSource::Mouse) { + if (!popupSubmenuFromAction(action, actionTop, source)) { + if (auto currentSubmenu = base::take(_activeSubmenu)) { + currentSubmenu->hideMenu(true); + } + } + } +} + +void PopupMenu::handleTriggered(QAction *action, int actionTop, TriggeredSource source) { + if (!popupSubmenuFromAction(action, actionTop, source)) { + hideMenu(); + _triggering = true; + emit action->trigger(); + _triggering = false; + if (_deleteLater) { + _deleteLater = false; + deleteLater(); + } + } +} + +bool PopupMenu::popupSubmenuFromAction(QAction *action, int actionTop, TriggeredSource source) { + if (auto submenu = _submenus.value(action)) { + if (_activeSubmenu == submenu) { + submenu->hideMenu(true); + } else { + popupSubmenu(submenu, actionTop, source); + } + return true; + } + return false; +} + +void PopupMenu::popupSubmenu(SubmenuPointer submenu, int actionTop, TriggeredSource source) { + if (auto currentSubmenu = base::take(_activeSubmenu)) { + currentSubmenu->hideMenu(true); + } + if (submenu) { + QPoint p(_inner.x() + (rtl() ? _padding.right() : _inner.width() - _padding.left()), _inner.y() + actionTop); + _activeSubmenu = submenu; + _activeSubmenu->showMenu(geometry().topLeft() + p, this, source); + + _menu->setChildShown(true); + } else { + _menu->setChildShown(false); + } +} + +void PopupMenu::forwardKeyPress(int key) { + if (!handleKeyPress(key)) { + _menu->handleKeyPress(key); + } +} + +bool PopupMenu::handleKeyPress(int key) { + if (_activeSubmenu) { + _activeSubmenu->handleKeyPress(key); + return true; + } else if (key == Qt::Key_Escape) { + hideMenu(_parent ? true : false); + return true; + } else if (key == (rtl() ? Qt::Key_Right : Qt::Key_Left)) { + if (_parent) { + hideMenu(true); + return true; + } + } + return false; +} + +void PopupMenu::handleMouseMove(QPoint globalPosition) { + if (_parent) { + _parent->forwardMouseMove(globalPosition); + } +} + +void PopupMenu::handleMousePress(QPoint globalPosition) { + if (_parent) { + _parent->forwardMousePress(globalPosition); + } else { + hideMenu(); + } +} + +void PopupMenu::focusOutEvent(QFocusEvent *e) { + hideMenu(); +} + +void PopupMenu::hideEvent(QHideEvent *e) { + if (_deleteOnHide) { + if (_triggering) { + _deleteLater = true; + } else { + deleteLater(); + } + } +} + +void PopupMenu::hideMenu(bool fast) { + if (isHidden()) return; + if (_parent && !_a_hide.animating()) { + _parent->childHiding(this); + } + if (fast) { + if (_a_hide.animating()) { + _a_hide.stop(); + } + a_opacity = anim::fvalue(0, 0); + hideFinish(); + } else { + if (!_a_hide.animating()) { + _cache = myGrab(this); + a_opacity.start(0); + _a_hide.start(); + } + if (_parent) { + _parent->hideMenu(); + } + } + if (_activeSubmenu) { + _activeSubmenu->hideMenu(fast); + } +} + +void PopupMenu::childHiding(PopupMenu *child) { + if (_activeSubmenu && _activeSubmenu == child) { + _activeSubmenu = SubmenuPointer(); + } +} + +void PopupMenu::hideFinish() { + hide(); +} + +void PopupMenu::step_hide(float64 ms, bool timer) { + float64 dt = ms / _st.duration; + if (dt >= 1) { + _a_hide.stop(); + a_opacity.finish(); + hideFinish(); + } else { + a_opacity.update(dt, anim::linear); + } + if (timer) update(); +} + +void PopupMenu::deleteOnHide(bool del) { + _deleteOnHide = del; +} + +void PopupMenu::popup(const QPoint &p) { + showMenu(p, nullptr, TriggeredSource::Mouse); +} + +void PopupMenu::showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource source) { + _parent = parent; + + QPoint w = p - QPoint(0, _padding.top()); + QRect r = Sandbox::screenGeometry(p); + if (rtl()) { + if (w.x() - width() < r.x() - _padding.left()) { + if (_parent && w.x() + _parent->width() - _padding.left() - _padding.right() + width() - _padding.right() <= r.x() + r.width()) { + w.setX(w.x() + _parent->width() - _padding.left() - _padding.right()); + } else { + w.setX(r.x() - _padding.left()); + } + } else { + w.setX(w.x() - width()); + } + } else { + if (w.x() + width() - _padding.right() > r.x() + r.width()) { + if (_parent && w.x() - _parent->width() + _padding.left() + _padding.right() - width() + _padding.right() >= r.x() - _padding.left()) { + w.setX(w.x() + _padding.left() + _padding.right() - _parent->width() - width() + _padding.left() + _padding.right()); + } else { + w.setX(r.x() + r.width() - width() + _padding.right()); + } + } + } + if (w.y() + height() - _padding.bottom() > r.y() + r.height()) { + if (_parent) { + w.setY(r.y() + r.height() - height() + _padding.bottom()); + } else { + w.setY(p.y() - height() + _padding.bottom()); + } + } + if (w.y() < r.y()) { + w.setY(r.y()); + } + move(w); + + _menu->setShowSource(source); + + psUpdateOverlayed(this); + show(); + psShowOverAll(this); + windowHandle()->requestActivate(); + activateWindow(); + + if (_a_hide.animating()) { + _a_hide.stop(); + _cache = QPixmap(); + } + a_opacity = anim::fvalue(1, 1); +} + +PopupMenu::~PopupMenu() { + for (auto submenu : base::take(_submenus)) { + delete submenu; + } +#if defined Q_OS_LINUX32 || defined Q_OS_LINUX64 + if (auto w = App::wnd()) { + w->onReActivate(); + QTimer::singleShot(200, w, SLOT(onReActivate())); + } +#endif +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/popup_menu.h b/Telegram/SourceFiles/ui/widgets/popup_menu.h new file mode 100644 index 000000000..7e9047465 --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/popup_menu.h @@ -0,0 +1,112 @@ +/* + This file is part of Telegram Desktop, + the official desktop version of Telegram messaging app, see https://telegram.org + + Telegram Desktop is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + It is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE + Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org + */ +#pragma once + +#include "styles/style_widgets.h" +#include "ui/effects/rect_shadow.h" +#include "ui/widgets/menu.h" + +namespace Ui { + +class PopupMenu : public TWidget { +public: + PopupMenu(const style::PopupMenu &st = st::defaultPopupMenu); + PopupMenu(QMenu *menu, const style::PopupMenu &st = st::defaultPopupMenu); + + QAction *addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon = nullptr); + QAction *addSeparator(); + void clearActions(); + + using Actions = Ui::Menu::Actions; + Actions &actions(); + + void deleteOnHide(bool del); + void popup(const QPoint &p); + void hideMenu(bool fast = false); + + ~PopupMenu(); + +protected: + void paintEvent(QPaintEvent *e) override; + void focusOutEvent(QFocusEvent *e) override; + void hideEvent(QHideEvent *e) override; + + void keyPressEvent(QKeyEvent *e) override { + forwardKeyPress(e->key()); + } + void mouseMoveEvent(QMouseEvent *e) override { + forwardMouseMove(e->globalPos()); + } + void mousePressEvent(QMouseEvent *e) override { + forwardMousePress(e->globalPos()); + } + +private: + void childHiding(PopupMenu *child); + + void step_hide(float64 ms, bool timer); + + void init(); + void hideFinish(); + + using TriggeredSource = Ui::Menu::TriggeredSource; + void handleMenuResize(); + void handleActivated(QAction *action, int actionTop, TriggeredSource source); + void handleTriggered(QAction *action, int actionTop, TriggeredSource source); + void forwardKeyPress(int key); + bool handleKeyPress(int key); + void forwardMouseMove(QPoint globalPosition) { + _menu->handleMouseMove(globalPosition); + } + void handleMouseMove(QPoint globalPosition); + void forwardMousePress(QPoint globalPosition) { + _menu->handleMousePress(globalPosition); + } + void handleMousePress(QPoint globalPosition); + + using SubmenuPointer = QPointer; + bool popupSubmenuFromAction(QAction *action, int actionTop, TriggeredSource source); + void popupSubmenu(SubmenuPointer submenu, int actionTop, TriggeredSource source); + void showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource source); + + const style::PopupMenu &_st; + + ChildWidget _menu; + + using Submenus = QMap; + Submenus _submenus; + + PopupMenu *_parent = nullptr; + + QRect _inner; + style::margins _padding; + + Ui::RectShadow _shadow; + SubmenuPointer _activeSubmenu; + + QPixmap _cache; + anim::fvalue a_opacity; + Animation _a_hide; + + bool _deleteOnHide = true; + bool _triggering = false; + bool _deleteLater = false; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/tooltip.cpp b/Telegram/SourceFiles/ui/widgets/tooltip.cpp new file mode 100644 index 000000000..a18da282c --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/tooltip.cpp @@ -0,0 +1,185 @@ +/* + This file is part of Telegram Desktop, + the official desktop version of Telegram messaging app, see https://telegram.org + + Telegram Desktop is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + It is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE + Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org + */ +#include "stdafx.h" +#include "ui/widgets/tooltip.h" + +#include "application.h" + +namespace Ui { + +Tooltip *TooltipInstance = nullptr; + +AbstractTooltipShower::~AbstractTooltipShower() { + if (TooltipInstance && TooltipInstance->_shower == this) { + TooltipInstance->_shower = 0; + } +} + +Tooltip::Tooltip() : TWidget(nullptr) { + TooltipInstance = this; + + setWindowFlags(Qt::WindowFlags(Qt::FramelessWindowHint) | Qt::BypassWindowManagerHint | Qt::ToolTip | Qt::NoDropShadowWindowHint); + setAttribute(Qt::WA_NoSystemBackground, true); + + _showTimer.setSingleShot(true); + connect(&_showTimer, SIGNAL(timeout()), this, SLOT(onShow())); + + connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWndActiveChanged())); +} + +void Tooltip::onShow() { + if (_shower) { + QString text = (App::wnd() && App::wnd()->isActive(false)) ? _shower->tooltipText() : QString(); + if (text.isEmpty()) { + Hide(); + } else { + TooltipInstance->popup(_shower->tooltipPos(), text, _shower->tooltipSt()); + } + } +} + +void Tooltip::onWndActiveChanged() { + if (!App::wnd() || !App::wnd()->windowHandle() || !App::wnd()->windowHandle()->isActive()) { + Tooltip::Hide(); + } +} + +bool Tooltip::eventFilter(QObject *o, QEvent *e) { + if (e->type() == QEvent::Leave) { + _hideByLeaveTimer.start(10); + } else if (e->type() == QEvent::Enter) { + _hideByLeaveTimer.stop(); + } else if (e->type() == QEvent::MouseMove) { + if ((QCursor::pos() - _point).manhattanLength() > QApplication::startDragDistance()) { + Hide(); + } + } + return TWidget::eventFilter(o, e); +} + +void Tooltip::onHideByLeave() { + Hide(); +} + +Tooltip::~Tooltip() { + if (TooltipInstance == this) { + TooltipInstance = 0; + } +} + +void Tooltip::popup(const QPoint &m, const QString &text, const style::Tooltip *st) { + if (!_hideByLeaveTimer.isSingleShot()) { + _hideByLeaveTimer.setSingleShot(true); + connect(&_hideByLeaveTimer, SIGNAL(timeout()), this, SLOT(onHideByLeave())); + + Sandbox::installEventFilter(this); + } + + _point = m; + _st = st; + _text = Text(_st->textFont, text, _textPlainOptions, _st->widthMax, true); + + int32 addw = 2 * st::lineWidth + _st->textPadding.left() + _st->textPadding.right(); + int32 addh = 2 * st::lineWidth + _st->textPadding.top() + _st->textPadding.bottom(); + + // count tooltip size + QSize s(addw + _text.maxWidth(), addh + _text.minHeight()); + if (s.width() > _st->widthMax) { + s.setWidth(addw + _text.countWidth(_st->widthMax - addw)); + s.setHeight(addh + _text.countHeight(s.width() - addw)); + } + int32 maxh = addh + (_st->linesMax * _st->textFont->height); + if (s.height() > maxh) { + s.setHeight(maxh); + } + + // count tooltip position + QPoint p(m + _st->shift); + if (rtl()) { + p.setX(m.x() - s.width() - _st->shift.x()); + } + if (s.width() < 2 * _st->shift.x()) { + p.setX(m.x() - (s.width() / 2)); + } + + // adjust tooltip position + QRect r(QApplication::desktop()->screenGeometry(m)); + if (r.x() + r.width() - _st->skip < p.x() + s.width() && p.x() + s.width() > m.x()) { + p.setX(qMax(r.x() + r.width() - int32(_st->skip) - s.width(), m.x() - s.width())); + } + if (r.x() + _st->skip > p.x() && p.x() < m.x()) { + p.setX(qMin(m.x(), r.x() + int32(_st->skip))); + } + if (r.y() + r.height() - _st->skip < p.y() + s.height()) { + p.setY(m.y() - s.height() - _st->skip); + } + if (r.y() > p.x()) { + p.setY(qMin(m.y() + _st->shift.y(), r.y() + r.height() - s.height())); + } + + setGeometry(QRect(p, s)); + + _hideByLeaveTimer.stop(); + show(); +} + +void Tooltip::paintEvent(QPaintEvent *e) { + Painter p(this); + + p.fillRect(rect(), _st->textBg); + + p.fillRect(QRect(0, 0, width(), st::lineWidth), _st->textBorder); + p.fillRect(QRect(0, height() - st::lineWidth, width(), st::lineWidth), _st->textBorder); + p.fillRect(QRect(0, st::lineWidth, st::lineWidth, height() - 2 * st::lineWidth), _st->textBorder); + p.fillRect(QRect(width() - st::lineWidth, st::lineWidth, st::lineWidth, height() - 2 * st::lineWidth), _st->textBorder); + + int32 lines = qFloor((height() - 2 * st::lineWidth - _st->textPadding.top() - _st->textPadding.bottom()) / _st->textFont->height); + + p.setPen(_st->textFg); + _text.drawElided(p, st::lineWidth + _st->textPadding.left(), st::lineWidth + _st->textPadding.top(), width() - 2 * st::lineWidth - _st->textPadding.left() - _st->textPadding.right(), lines); +} + +void Tooltip::hideEvent(QHideEvent *e) { + if (TooltipInstance == this) { + Hide(); + } +} + +void Tooltip::Show(int32 delay, const AbstractTooltipShower *shower) { + if (!TooltipInstance) { + new Tooltip(); + } + TooltipInstance->_shower = shower; + if (delay >= 0) { + TooltipInstance->_showTimer.start(delay); + } else { + TooltipInstance->onShow(); + } +} + +void Tooltip::Hide() { + if (auto instance = TooltipInstance) { + TooltipInstance = nullptr; + instance->_showTimer.stop(); + instance->_hideByLeaveTimer.stop(); + instance->hide(); + instance->deleteLater(); + } +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/tooltip.h b/Telegram/SourceFiles/ui/widgets/tooltip.h new file mode 100644 index 000000000..eb1611014 --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/tooltip.h @@ -0,0 +1,69 @@ +/* + This file is part of Telegram Desktop, + the official desktop version of Telegram messaging app, see https://telegram.org + + Telegram Desktop is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + It is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE + Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org + */ +#pragma once + +namespace Ui { + +class AbstractTooltipShower { +public: + virtual QString tooltipText() const = 0; + virtual QPoint tooltipPos() const = 0; + virtual const style::Tooltip *tooltipSt() const { + return &st::defaultTooltip; + } + virtual ~AbstractTooltipShower(); +}; + +class Tooltip : public TWidget { + Q_OBJECT + +public: + static void Show(int32 delay, const AbstractTooltipShower *shower); + static void Hide(); + + private slots: + void onShow(); + void onWndActiveChanged(); + void onHideByLeave(); + +protected: + void paintEvent(QPaintEvent *e) override; + void hideEvent(QHideEvent *e) override; + + bool eventFilter(QObject *o, QEvent *e) override; + +private: + Tooltip(); + ~Tooltip(); + + void popup(const QPoint &p, const QString &text, const style::Tooltip *st); + + friend class AbstractTooltipShower; + const AbstractTooltipShower *_shower = nullptr; + QTimer _showTimer; + + Text _text; + QPoint _point; + + const style::Tooltip *_st = nullptr; + + QTimer _hideByLeaveTimer; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/widgets.style b/Telegram/SourceFiles/ui/widgets/widgets.style index 60453cbb2..5eda18b3c 100644 --- a/Telegram/SourceFiles/ui/widgets/widgets.style +++ b/Telegram/SourceFiles/ui/widgets/widgets.style @@ -99,6 +99,60 @@ MultiSelect { fieldCancelSkip: pixels; } +Menu { + skip: pixels; + + itemBg: color; + itemBgOver: color; + itemFg: color; + itemFgOver: color; + itemFgDisabled: color; + itemFgShortcut: color; + itemFgShortcutOver: color; + itemFgShortcutDisabled: color; + itemPadding: margins; + itemIconPosition: point; + itemIconOpacity: double; + itemIconOverOpacity: double; + itemFont: font; + + separatorPadding: margins; + separatorWidth: pixels; + separatorFg: color; + + arrow: icon; + + widthMin: pixels; + widthMax: pixels; +} + +PopupMenu { + shadow: icon; + shadowShift: pixels; + + menu: Menu; + + duration: int; +} + +InnerDropdown { + padding: margins; + shadow: icon; + shadowShift: pixels; + + duration: int; + width: pixels; + + scroll: flatScroll; + scrollMargin: margins; + scrollPadding: margins; +} + +DropdownMenu { + wrap: InnerDropdown; + menu: Menu; +} + widgetSlideDuration: 200; widgetFadeDuration: 200; @@ -112,3 +166,52 @@ discreteSliderLabelTop: 17px; discreteSliderLabelFont: normalFont; discreteSliderLabelFg: #1485c2; discreteSliderDuration: 200; + +defaultMenuArrow: icon {{ "dropdown_submenu_arrow", #373737 }}; +defaultMenu: Menu { + skip: 5px; + + itemBg: white; + itemBgOver: overBg; + itemFg: black; + itemFgOver: black; + itemFgDisabled: #ccc; + itemFgShortcut: #999; + itemFgShortcutOver: #7c99b2; + itemFgShortcutDisabled: #ccc; + itemIconPosition: point(0px, 0px); + itemIconOpacity: 1.; + itemIconOverOpacity: 1.; + itemPadding: margins(17px, 8px, 17px, 7px); + itemFont: normalFont; + + separatorPadding: margins(0px, 5px, 0px, 5px); + separatorWidth: 1px; + separatorFg: #f1f1f1; + + arrow: defaultMenuArrow; + + widthMin: 180px; + widthMax: 300px; +} +defaultPopupMenu: PopupMenu { + shadow: defaultDropdownShadow; + shadowShift: defaultDropdownShadowShift; + + menu: defaultMenu; + + duration: 120; +} +defaultInnerDropdown: InnerDropdown { + padding: margins(10px, 10px, 10px, 10px); + shadow: defaultDropdownShadow; + shadowShift: defaultDropdownShadowShift; + + duration: 150; + + scroll: solidScroll; +} +defaultDropdownMenu: DropdownMenu { + wrap: defaultInnerDropdown; + menu: defaultMenu; +} diff --git a/Telegram/SourceFiles/window/top_bar_widget.h b/Telegram/SourceFiles/window/top_bar_widget.h index df5f6a41a..1c610778b 100644 --- a/Telegram/SourceFiles/window/top_bar_widget.h +++ b/Telegram/SourceFiles/window/top_bar_widget.h @@ -27,7 +27,6 @@ class PeerAvatarButton; class RoundButton; class IconButton; } // namespace Ui -class IconedButton; namespace Window { diff --git a/Telegram/gyp/Telegram.gyp b/Telegram/gyp/Telegram.gyp index b01d35aaf..012edecf9 100644 --- a/Telegram/gyp/Telegram.gyp +++ b/Telegram/gyp/Telegram.gyp @@ -111,8 +111,6 @@ '<(src_loc)/config.h', '<(src_loc)/dialogswidget.cpp', '<(src_loc)/dialogswidget.h', - '<(src_loc)/dropdown.cpp', - '<(src_loc)/dropdown.h', '<(src_loc)/facades.cpp', '<(src_loc)/facades.h', '<(src_loc)/fileuploader.cpp', @@ -241,6 +239,8 @@ '<(src_loc)/dialogs/dialogs_row.h', '<(src_loc)/history/field_autocomplete.cpp', '<(src_loc)/history/field_autocomplete.h', + '<(src_loc)/history/history_drag_area.cpp', + '<(src_loc)/history/history_drag_area.h', '<(src_loc)/history/history_item.cpp', '<(src_loc)/history/history_item.h', '<(src_loc)/history/history_location_manager.cpp', @@ -489,22 +489,30 @@ '<(src_loc)/ui/widgets/continuous_slider.h', '<(src_loc)/ui/widgets/discrete_slider.cpp', '<(src_loc)/ui/widgets/discrete_slider.h', + '<(src_loc)/ui/widgets/dropdown_menu.cpp', + '<(src_loc)/ui/widgets/dropdown_menu.h', '<(src_loc)/ui/widgets/filled_slider.cpp', '<(src_loc)/ui/widgets/filled_slider.h', + '<(src_loc)/ui/widgets/inner_dropdown.cpp', + '<(src_loc)/ui/widgets/inner_dropdown.h', '<(src_loc)/ui/widgets/label_simple.cpp', '<(src_loc)/ui/widgets/label_simple.h', '<(src_loc)/ui/widgets/media_slider.cpp', '<(src_loc)/ui/widgets/media_slider.h', + '<(src_loc)/ui/widgets/menu.cpp', + '<(src_loc)/ui/widgets/menu.h', '<(src_loc)/ui/widgets/multi_select.cpp', '<(src_loc)/ui/widgets/multi_select.h', + '<(src_loc)/ui/widgets/popup_menu.cpp', + '<(src_loc)/ui/widgets/popup_menu.h', '<(src_loc)/ui/widgets/shadow.cpp', '<(src_loc)/ui/widgets/shadow.h', + '<(src_loc)/ui/widgets/tooltip.cpp', + '<(src_loc)/ui/widgets/tooltip.h', '<(src_loc)/ui/animation.cpp', '<(src_loc)/ui/animation.h', '<(src_loc)/ui/button.cpp', '<(src_loc)/ui/button.h', - '<(src_loc)/ui/popupmenu.cpp', - '<(src_loc)/ui/popupmenu.h', '<(src_loc)/ui/countryinput.cpp', '<(src_loc)/ui/countryinput.h', '<(src_loc)/ui/emoji_config.cpp', @@ -523,8 +531,6 @@ '<(src_loc)/ui/flattextarea.h', '<(src_loc)/ui/images.cpp', '<(src_loc)/ui/images.h', - '<(src_loc)/ui/inner_dropdown.cpp', - '<(src_loc)/ui/inner_dropdown.h', '<(src_loc)/ui/scrollarea.cpp', '<(src_loc)/ui/scrollarea.h', '<(src_loc)/ui/twidget.cpp', diff --git a/Telegram/gyp/codegen_rules.gypi b/Telegram/gyp/codegen_rules.gypi index 361723685..058098b9d 100644 --- a/Telegram/gyp/codegen_rules.gypi +++ b/Telegram/gyp/codegen_rules.gypi @@ -33,6 +33,7 @@ 'action': [ '<(PRODUCT_DIR)/codegen_style<(exe_ext)', '-I<(res_loc)', '-I<(src_loc)', + '-w<(PRODUCT_DIR)/../..', '--skip-styles', '<(res_loc)/basic.style', ], 'message': 'Updating sprites..',