Compare commits

..

242 Commits

Author SHA1 Message Date
John Preston
3cdd115317 Beta version 2.8.6: Fix build for Linux. 2021-07-07 00:54:56 +03:00
John Preston
bdd1d2484c Beta version 2.8.6: Update build script. 2021-07-06 20:32:08 +03:00
John Preston
d5a416d5ea Beta version 2.8.6: Fix build for macOS. 2021-07-06 20:25:46 +03:00
John Preston
46d393ea0f Beta version 2.8.6.
- Added a simple image editor.
Crop photos or highlight parts of screenshots before sending.
- Use Direct3D 9 backend in ANGLE by default (Windows).
- Fix "Show in Finder" not focusing the Finder window (macOS).
- Use GTK from a child process (Linux).
2021-07-06 17:17:23 +03:00
nyakze
68e351b7c8 Attempt to fix not working commands
Attempt to fix not working commands by using more conventional keys
2021-07-06 17:16:46 +03:00
John Preston
6f9c911faa Fix Qt build instructions with ANGLE. 2021-07-06 17:04:05 +03:00
John Preston
dd381d9b56 Show vector elements count in DebugLogs. 2021-07-06 16:54:26 +03:00
John Preston
8eedc7b2ba Fix build on Windows. 2021-07-06 15:56:43 +03:00
John Preston
9ba1af2eb9 Use tg_angle fork. 2021-07-06 15:55:49 +03:00
23rd
f567328a60 Fixed deleting item after saving result of scene in photo editor. 2021-07-06 14:15:07 +03:00
23rd
953fa52490 Fixed updating of undo state on deleting item in photo editor. 2021-07-06 14:15:07 +03:00
23rd
b1477260f0 Improved content margins in photo editor. 2021-07-06 14:15:07 +03:00
23rd
038de9ef15 Changed behavior to keep StickerSetBox until pack is archived. 2021-07-06 12:13:06 +03:00
23rd
4701c5d6e3 Added ability to archive mask packs. 2021-07-06 12:13:06 +03:00
23rd
8420b7dc17 Disabled ability to use hotkeys when selecting color in photo editor. 2021-07-06 12:13:06 +03:00
23rd
9dacf69d41 Fixed line drawing on mode switching in photo editor. 2021-07-06 12:13:06 +03:00
23rd
a91efd9164 Fixed ability to draw blank lines in photo editor. 2021-07-06 12:13:06 +03:00
23rd
a631a28092 Removed App::pixmapFromImageInPlace. 2021-07-06 12:13:06 +03:00
23rd
7bcb1fc8b2 Fixed borders of drawn lines in photo editor. 2021-07-06 12:13:06 +03:00
23rd
cdafd8f171 Fixed width of preview for small media in SendFilesBox.
Regression was introduced in 42d4fdb89f.
2021-07-06 12:13:06 +03:00
23rd
116aa01e51 Refactored column width in SingleMediaPreview. 2021-07-06 12:13:06 +03:00
23rd
96b40f43e9 Added ability to drag and drop images in photo editor. 2021-07-06 12:13:06 +03:00
23rd
a93ec9c2c2 Added Escape hotkey to clear selection in photo editor. 2021-07-06 12:13:06 +03:00
23rd
3ee3919d50 Added hotkeys for switching between modes in photo editor. 2021-07-06 12:13:06 +03:00
23rd
b4410c49b9 Added background color in photo editor. 2021-07-06 12:13:06 +03:00
23rd
82bf6ca94f Fixed display of sticker panel on mode change in photo editor. 2021-07-06 12:13:06 +03:00
23rd
785ebfee34 Added animation to button bars in photo editor. 2021-07-06 12:13:06 +03:00
23rd
a60f8d75a0 Added second button bar for paint mode in photo editor. 2021-07-06 12:13:06 +03:00
23rd
5976a7ed19 Moved edge buttons to bar in photo editor. 2021-07-06 12:13:06 +03:00
23rd
df7026b59c Made alignment to bottom for button strip in photo editor. 2021-07-06 12:13:06 +03:00
23rd
edfd9bedc1 Improved style for controls in EditCaptionBox. 2021-07-06 12:13:06 +03:00
23rd
f52c6a6daa Improved style for controls in SendFilesBox. 2021-07-06 12:13:06 +03:00
23rd
af10b6d487 Added icons for SendFilesBox. 2021-07-06 12:13:06 +03:00
23rd
e30eacff41 Added photo editor hint to SendFilesBox. 2021-07-06 12:13:06 +03:00
23rd
18154e403a Added ability to open photo editor in SendFilesBox with left-click. 2021-07-06 12:13:06 +03:00
23rd
6975b04e6b Fixed triggering of pressed buttons in album preview. 2021-07-06 12:13:06 +03:00
23rd
948302cf02 Added setting to hide photo editor hint. 2021-07-06 12:13:06 +03:00
23rd
e4cff8cb4b Added photo editor hint to EditCaptionBox. 2021-07-06 12:13:06 +03:00
23rd
5bd17ae1b2 Fixed caption area height in EditCaptionBox.
Regression was introduced in 51f960442e.
2021-07-06 12:13:06 +03:00
23rd
22213a71c1 Added ability to open photo editor in EditCaptionBox with left-click. 2021-07-06 12:13:06 +03:00
23rd
e926e5f882 Slightly improved style of controls in photo editor. 2021-07-06 12:13:06 +03:00
23rd
221d45b500 Updated control icons for photo editor. 2021-07-06 12:13:06 +03:00
23rd
6bb7e2c2eb Removed using of hardcoded numbers of tray menu actions. 2021-07-06 12:13:06 +03:00
23rd
2a86ce596d Added shortcuts for actions of items in photo editor. 2021-07-06 12:13:06 +03:00
23rd
f936e484cc Removed unused types from scene items. 2021-07-06 12:13:05 +03:00
23rd
b2a1c10036 Removed masks panel when there are no masks. 2021-07-06 12:13:05 +03:00
23rd
2a58d01927 Removed search and featured buttons from masks panel. 2021-07-06 12:13:05 +03:00
23rd
7cd6b821b3 Fixed update of recently attached stickers after sending. 2021-07-06 12:13:05 +03:00
23rd
de108c8efe Fixed removing masks set from StickersListWidget. 2021-07-06 12:13:05 +03:00
23rd
e7104b5ebe Added support for archived masks. 2021-07-06 12:13:05 +03:00
23rd
2d17bd02a3 Moved mask management to separate box. 2021-07-06 12:13:05 +03:00
23rd
2a3115f461 Fixed phrases to display mask count. 2021-07-06 12:13:05 +03:00
23rd
8d62800e77 Moved stickerSetInstalled from ApiWrap to Data::Stickers. 2021-07-06 12:13:05 +03:00
23rd
7e04bf9533 Added ability to install mask sets. 2021-07-06 12:13:05 +03:00
23rd
2bd3a8aaff Added ability to delete and reorder mask sets.
Moved ApiWrap::stickerSetDisenabled and ApiWrap::stickersSaveOrder
to ApiWrap::saveStickerSets as lambdas.
2021-07-06 12:13:05 +03:00
23rd
70f92a7817 Added initial masks tab to manage stickers box. 2021-07-06 12:13:05 +03:00
23rd
8e08f69508 Added support updateNewStickerSet and updateStickerSets for masks. 2021-07-06 12:13:05 +03:00
23rd
abe62475cb Added support updateStickerSetsOrder for masks. 2021-07-06 12:13:05 +03:00
23rd
1cdb83462e Added initial implementation of masks panel. 2021-07-06 12:13:05 +03:00
23rd
d9a29b6f15 Fixed item pen width in big images. 2021-07-06 12:13:05 +03:00
23rd
1504f92a64 Fixed size limits of item on big images. 2021-07-06 12:13:05 +03:00
23rd
36e5056b59 Fixed selection of items on mode switching. 2021-07-06 12:13:05 +03:00
23rd
c5c707f0fd Fixed independence of item and scene transforms when adding. 2021-07-06 12:13:05 +03:00
23rd
832dd8d50c Moved some photo editor files to separate directories. 2021-07-06 12:13:05 +03:00
23rd
7d2b20e624 Made TabbedSelector more flexible. 2021-07-06 12:13:05 +03:00
23rd
049945a9b9 Added ability to duplicate items in photo editor. 2021-07-06 12:13:05 +03:00
23rd
808c9e3d2c Added ability to flip items in photo editor. 2021-07-06 12:13:05 +03:00
23rd
fde7cef9c8 Removed using of raw pointers for QGraphicsItem in photo editor.
Now all items are wrapped in the shared_ptr,
and the Scene loses ownership of all items before being destroyed.
2021-07-06 12:13:05 +03:00
23rd
2791f89f30 Added initial context menu to items in photo editor. 2021-07-06 12:13:05 +03:00
23rd
858b5831e8 Fixed clearing of redo list after adding sticker item in photo editor. 2021-07-06 12:13:05 +03:00
23rd
9166423598 Fixed multi selection of items in photo editor. 2021-07-06 12:13:05 +03:00
23rd
184d984336 Added ability to snap rotation of items with Shift key in photo editor. 2021-07-06 12:13:05 +03:00
23rd
0b5044f064 Fixed size of handles of base item in photo editor. 2021-07-06 12:13:05 +03:00
23rd
274b66f74b Added ability to create items in photo editor with different ratios. 2021-07-06 12:13:05 +03:00
23rd
e05343d721 Added sending info of stickered photos. 2021-07-06 12:13:05 +03:00
23rd
bc316a2536 Removed Storage::UploadedThumbDocument struct. 2021-07-06 12:13:05 +03:00
23rd
a6904be81d Slightly optimized mouse painting in photo editor. 2021-07-06 12:13:05 +03:00
23rd
690a7d1608 Fixed undo and redo paint actions. 2021-07-06 12:13:05 +03:00
23rd
a3e54fcd7c Moved draft painting in photo editor to separate files. 2021-07-06 12:13:05 +03:00
23rd
23c67bb2a2 Added ability to add stickers to photo in photo editor. 2021-07-06 12:13:05 +03:00
23rd
75367f0488 Added sticker panel to photo editor. 2021-07-06 12:13:05 +03:00
23rd
216ffad80e Added container of controllers for photo editor. 2021-07-06 12:13:05 +03:00
23rd
c312607ff8 Added stickers panel controller for photo editor. 2021-07-06 12:13:05 +03:00
23rd
812d616f66 Added scene base item for photo editor. 2021-07-06 12:13:05 +03:00
23rd
183408cb2d Added button highlighting for flipped image to photo editor. 2021-07-06 12:13:05 +03:00
23rd
1a7d5b7c95 Removed unused photo crop box. 2021-07-06 12:13:05 +03:00
23rd
17465e1082 Replaced old photo crop box with photo editor for profile photos. 2021-07-06 12:13:05 +03:00
23rd
a996b14291 Fixed keeping of aspect ratio in crop widget. 2021-07-06 12:13:05 +03:00
23rd
2045252cfd Added ability to pass data for photo editor. 2021-07-06 12:13:05 +03:00
23rd
a2e674bdb6 Added Window::Controller pointer to data of intro widget. 2021-07-06 12:13:05 +03:00
23rd
cc4055a5e3 Added method to Window::Controller to show custom layer widget. 2021-07-06 12:13:05 +03:00
23rd
d1b6cf1fae Added draft menu to EditCaptionBox to open photo editor. 2021-07-06 12:13:05 +03:00
23rd
671a06c407 Replaced using of QPixmap in photo editor with Image. 2021-07-06 12:13:05 +03:00
23rd
3ce315111f Added draft menu to SendFilesBox to open photo editor. 2021-07-06 12:13:05 +03:00
23rd
09768ce28a Refactored crop widget in photo editor. 2021-07-06 12:13:05 +03:00
23rd
c9affe0da5 Added custom layer widget with photo editor. 2021-07-06 12:13:05 +03:00
23rd
4909ba5a1e Added ability to pass custom layer widgets to stack. 2021-07-06 12:13:05 +03:00
23rd
e322733e20 Added saving of color and size of brush from photo editor to settings. 2021-07-06 12:13:05 +03:00
23rd
dc7f440902 Added color picker to photo editor. 2021-07-06 12:13:05 +03:00
23rd
4849376347 Added ability to undo and to redo paint actions in photo editor. 2021-07-06 12:13:05 +03:00
23rd
8eca57f419 Added saving and discarding between modes in photo editor. 2021-07-06 12:13:05 +03:00
23rd
0adcd37030 Added edge buttons to controls of photo editor. 2021-07-06 12:13:05 +03:00
23rd
2bdb9af146 Added dummy control icons for photo editor. 2021-07-06 12:13:05 +03:00
23rd
5b6bddd7fc Added initial implementation of mouse drawing in photo editor. 2021-07-06 12:13:05 +03:00
23rd
e1ea833ad6 Added ability to crop images in photo editor. 2021-07-06 12:13:05 +03:00
23rd
85c21ba0e4 Added ability to open photo editor with saved modifications. 2021-07-06 12:13:05 +03:00
23rd
4d72d20398 Added ability to send modified images. 2021-07-06 12:13:04 +03:00
23rd
9d3d16a725 Added initial ability to rotate and flip image to photo editor. 2021-07-06 12:13:04 +03:00
23rd
99deaf6005 Added dummy buttons to PhotoEditorControls. 2021-07-06 12:13:04 +03:00
23rd
d8921c7cf5 Added HorizontalContainer for buttons to PhotoEditorControls. 2021-07-06 12:13:04 +03:00
23rd
f7fa36ca1d Added photo painting to PhotoEditorContent. 2021-07-06 12:13:04 +03:00
23rd
45f8e68203 Initialized empty files of photo editor. 2021-07-06 12:13:04 +03:00
John Preston
2b47d6d63f Add depot_tools information to build instructions. 2021-07-06 12:05:45 +03:00
John Preston
aaefeed3f1 Support custom keyboard placeholders. 2021-07-05 21:12:52 +03:00
John Preston
7f00065bd8 Update API scheme to layer 130. 2021-07-05 21:12:52 +03:00
Ilya Fedin
6f031a715e Revert "Use QMenuBar instead of own global menu implementation on Linux"
This reverts commit 79f96480c2.
2021-07-05 21:01:20 +03:00
John Preston
6981ae605a Fix fake unread status reset on account change. 2021-07-05 19:48:06 +03:00
John Preston
d91c21fb26 Try enabling OpenGL back after switching to ANGLE. 2021-07-05 19:48:06 +03:00
John Preston
c95f052e60 Fix "Show in Finder" app focus bug by a Qt patch. 2021-07-05 19:31:21 +03:00
John Preston
c33be27b3c Update lib_webview submodule. 2021-07-05 15:57:55 +03:00
John Preston
6be9b25e99 Submit voice chat boxes by Enter. 2021-07-05 15:37:34 +03:00
Ilya Fedin
0bb391937e Update lib_webview 2021-07-04 20:05:53 +03:00
Ilya Fedin
75ff7a6637 Control GtkOpenWithDialog lifetime from outside 2021-07-04 20:05:53 +03:00
John Preston
aece7c1096 Remove testing code in stickers view. 2021-07-02 22:11:34 +03:00
John Preston
d2e6e7adf2 Fix build on macOS. 2021-07-02 22:11:23 +03:00
John Preston
b930bc0e6d Track bot commands separately in different chats. 2021-07-02 20:41:48 +03:00
John Preston
93d99d6173 Track only strings in BotCommand struct. 2021-07-02 20:41:46 +03:00
John Preston
a8df3dcf91 Remove test code for animated path thumbnails. 2021-07-02 20:40:32 +03:00
John Preston
b22e2ffe1d Animate inline path thumbnails with sliding gradient. 2021-07-02 20:40:32 +03:00
Ilya Fedin
22d23c8be1 Add missed signalId check 2021-07-02 20:11:28 +03:00
Ilya Fedin
b335741f99 Use gsl::finally to pop thread context where appropriate 2021-07-02 20:11:28 +03:00
Ilya Fedin
1261c775d4 Fix freeze after creating file dialog 2021-07-02 15:51:58 +03:00
John Preston
01b4a24ac7 Save sticker path thumbnails to local storage. 2021-07-02 13:39:08 +03:00
John Preston
4124c2eb57 Show inline path thumbnails for stickers. 2021-07-02 13:13:48 +03:00
John Preston
f09b91ebb5 Beta version 2.8.5: Fix build on non-Windows. 2021-07-02 04:45:58 +03:00
John Preston
57b147e0c8 Beta version 2.8.5.
- Use ANGLE for OpenGL over DirectX 9 / DirectX 11 (Windows).
- Use GTK from a child process (Linux).
2021-07-02 00:59:51 +03:00
Ilya Fedin
551ea7d879 Move GTK integration out of process with D-Bus 2021-07-02 00:59:36 +03:00
John Preston
d3c9bb0bc6 Try disabling native child OpenGL workaround. 2021-07-02 00:37:27 +03:00
John Preston
c711b1f1df Fix build on non-Linux systems. 2021-07-02 00:27:25 +03:00
23rd
af7ea90246 Added floating panel of playing playlist for scheduled audio files. 2021-07-01 23:53:45 +03:00
23rd
348cf4829c Added ability to scroll media in section of scheduled messages.
Fixed #8388.
2021-07-01 23:53:45 +03:00
23rd
4753a57091 Added ability to validate playlists in section of scheduled messages. 2021-07-01 23:53:45 +03:00
23rd
1a93f4fa4c Added ability to sparse id slices of scheduled media. 2021-07-01 23:53:45 +03:00
23rd
118fd187e3 Added abstract class for sparse ids slices. 2021-07-01 23:53:45 +03:00
23rd
baa47bde7f Removed unused MsgRange from SparseIdsSlice. 2021-07-01 23:53:45 +03:00
John Preston
18b48df9ce Allow to choose ANGLE backend. 2021-07-01 23:48:18 +03:00
John Preston
e71fc60d22 Use exact revision of ANGLE. 2021-07-01 23:48:16 +03:00
John Preston
148af59615 Don't check dll-s if "SetDefaultDllDirectories" is available. 2021-07-01 23:47:12 +03:00
John Preston
5b2db4112f Don't allow any .dll-s near Telegram.exe 2021-07-01 23:47:12 +03:00
John Preston
7cedc1f7a5 Add dynamic DirectX loading helper. 2021-07-01 23:47:08 +03:00
John Preston
6cea7d4a52 Fix YUV->RGB on D3D9 ANGLE backend. 2021-07-01 23:46:52 +03:00
John Preston
bd93aed393 Test build with statically linked ANGLE. 2021-07-01 23:46:52 +03:00
John Preston
348666de6d Use media viewer size hack only when required. 2021-07-01 23:46:52 +03:00
Ilya Fedin
47e32bebe4 Remove not really needed gtk scale factor query 2021-07-01 22:13:50 +03:00
Ilya Fedin
0b21c04489 Remove the copy of gtk file dialog 2021-07-01 22:13:50 +03:00
Ilya Fedin
85f013ebdb Revert "Avoid removing portal platformtheme plugin in snap"
This reverts commit 12db51fe75.
2021-07-01 22:13:50 +03:00
Ilya Fedin
832cc6ac69 Build Qt with gtk integration 2021-07-01 22:13:50 +03:00
Ilya Fedin
30ce049f51 Update submodules 2021-07-01 22:13:20 +03:00
Ilya Fedin
d42fb6d1b9 Switch from mallocng to jemalloc
Now it's known how to make it free the memory in an expected manner and it's better maintained
2021-07-01 22:13:20 +03:00
John Preston
cade53aa0a Version 2.8.4.
- Crash fixes in WebView on Windows.
2021-07-01 11:05:21 +03:00
GitHub Action
2fdcda7536 Update User-Agent for DNS to Chrome 91.0.4472.114. 2021-07-01 10:48:17 +03:00
Ilya Fedin
7e6439e4f8 Fix counting screen bottom point when restoring geometry 2021-06-30 00:27:39 +03:00
Ilya Fedin
f07ee7f590 Update lib_base and cmake_helpers 2021-06-29 17:35:39 +03:00
Ilya Fedin
02db4e01fa Get rid of qt5ct 2021-06-29 17:35:39 +03:00
Ilya Fedin
8d75078a42 Use Glib::MainLoop instead of QEventLoop in glib code 2021-06-29 15:10:08 +03:00
John Preston
0e25ef7524 Handle WinRT exceptions in WebView. 2021-06-29 14:08:50 +03:00
John Preston
8608d8aa4d Crash Fix: Destroy WebView before the container. 2021-06-29 11:07:47 +03:00
sammiee5311
c1a7332a5e Shorten if statement
Shorten if statement
2021-06-29 10:38:35 +03:00
Ilya Fedin
c3fb392906 Clean dbus-specific code in main_window_linux.h 2021-06-29 10:30:48 +03:00
Ilya Fedin
a59bfdb2f8 Fix handleNativeSurfaceChanged when dbus integration is disabled 2021-06-29 10:30:48 +03:00
Ilya Fedin
79f96480c2 Use QMenuBar instead of own global menu implementation on Linux 2021-06-29 10:30:48 +03:00
John Preston
60cbd96d91 Version 2.8.3.
- Fix crashes in OpenSSL on macOS.
2021-06-28 13:51:06 +03:00
John Preston
ee0400f1ac Version 2.8.2.
- Attempt to fix random crashes on macOS.

Fixes #16504.
2021-06-28 08:57:40 +03:00
John Preston
b8a3746558 Version 2.8.1: Fix NuGet WinRT header generation. 2021-06-26 13:26:39 +03:00
John Preston
14f25fc997 Version 2.8.1.
- Fix crash in audio player volume slider.
2021-06-26 13:03:40 +03:00
John Preston
27da6ee9eb Update patches revision in instructions. 2021-06-26 13:00:16 +03:00
John Preston
48d482006a Fix crash fix. 2021-06-26 12:33:18 +03:00
John Preston
9afee2620a Fix crash in vertical sliders.
Regression was introduced in 90ff8ecd0f.
2021-06-26 08:20:37 +03:00
John Preston
baca3047d4 Version 2.8: Fix build on Windows x64. 2021-06-24 18:39:41 +04:00
John Preston
43a5265e0c Version 2.8.
- Start video conferences from Voice Chats in any group.
- Share your screen or video from your camera
with up to 30 participants (limit to be increased soon).
- Talk without video with an unlimited number of participants.
- Create voice chats from the info page
of any group where you are an admin.
- Group video calls are supported natively on all devices,
including iPads and laptops.
2021-06-24 17:57:09 +04:00
John Preston
5519bb3523 Allow reporting private groups as well.
Fixes #7451.
2021-06-24 17:44:55 +04:00
John Preston
ff213d1386 Enable /LARGEADDRESSAWARE for 32 bit Windows build. 2021-06-24 17:44:55 +04:00
John Preston
55b3f99653 Fix new formatting mixing with emoji. 2021-06-24 17:44:55 +04:00
John Preston
8a6ff3f414 Add separator above volume control in voice chats. 2021-06-24 17:44:55 +04:00
John Preston
feb8624d05 Don't use private Hunspell headers. 2021-06-24 17:44:55 +04:00
John Preston
a2c33545d4 Improve some paddings. 2021-06-24 17:44:28 +04:00
23rd
7decf68122 Fixed possible crash in OverlayWidget when video continues from PiP. 2021-06-24 17:44:28 +04:00
John Preston
6b62ec97c6 Fix possible crash in export panel management. 2021-06-24 11:57:27 +04:00
Ilya Fedin
ea3dab4a06 Update lib_base 2021-06-24 11:26:24 +04:00
Ilya Fedin
5c8f08fc92 Move preview support from QGtkDialog to GtkFileDialog 2021-06-24 11:26:24 +04:00
Ilya Fedin
00a0b2c8b6 Get rid of GTK cast templates 2021-06-24 11:26:24 +04:00
Ilya Fedin
007218cc13 Use C++ wrappers in GtkOpenWithDialog 2021-06-24 11:26:24 +04:00
Ilya Fedin
8afe495a4f Avoid using g_unix_fd_list_new_from_array 2021-06-24 11:26:24 +04:00
Ilya Fedin
257f2086d1 Get rid of gtk2 header compatibility 2021-06-24 11:26:24 +04:00
Ilya Fedin
f011c84ce8 Make Linux file dialog API better 2021-06-24 11:26:24 +04:00
Ilya Fedin
8b839f46b2 Fix crash report window scale 2021-06-24 11:26:08 +04:00
23rd
c1067d8fe1 Fixed possible crash in notifications manager. 2021-06-24 11:25:36 +04:00
23rd
bb76818cc8 Split adaptive changed rpl::producer into two. 2021-06-24 11:25:35 +04:00
John Preston
5dcc219f1c For large video tile always request full quality. 2021-06-24 10:57:23 +04:00
John Preston
4ff9e90153 Add some assertions and logging for a crash debugging. 2021-06-24 10:49:01 +04:00
John Preston
28fe98af80 Add some assertions for a strange crash debugging. 2021-06-24 10:24:52 +04:00
John Preston
5eba65aaa0 Remove unused legacy protocol code. 2021-06-24 09:55:57 +04:00
John Preston
d1e3e7d240 Don't show pinned tooltips if only one video. 2021-06-23 20:14:49 +04:00
John Preston
90ff8ecd0f Fix volume slider in voice chats. 2021-06-23 20:14:22 +04:00
John Preston
468e75a572 Update submodules. 2021-06-23 20:13:19 +04:00
John Preston
ae3e5487d7 Fix editing messages with monospace parts. 2021-06-23 15:55:29 +04:00
John Preston
03147a5426 Fix possible crash in case of API error. 2021-06-23 14:29:38 +04:00
John Preston
14a2b10989 Show error if camera could not be enabled. 2021-06-23 12:04:05 +04:00
John Preston
b29f8aa1e6 Remove background over highlight in volume change item. 2021-06-23 11:07:23 +04:00
John Preston
f9bb932cd8 Fix voice chat window expanding near the screen edges. 2021-06-23 10:52:04 +04:00
John Preston
a38cbbf7e8 Fix disappearing icons after popup menu display. 2021-06-23 10:43:51 +04:00
John Preston
d5bb1717e0 Beta version 2.7.10: Fix link on macOS. 2021-06-22 23:07:33 +04:00
John Preston
520ff8f2ce Beta version 2.7.10: Fix build with Xcode. 2021-06-22 21:25:03 +04:00
John Preston
b3848f6a84 Beta version 2.7.10: Fix screencasts. 2021-06-22 21:23:01 +04:00
John Preston
518f387e0c Beta version 2.7.10: Update version. 2021-06-22 20:00:35 +04:00
John Preston
635f76a312 Beta version 2.7.10.
- Added ability to mix together bold, italic and other formatting.
- Fix voice chats and video calls OpenGL with some drivers on Windows.
- Several bug fixes.
2021-06-22 19:59:22 +04:00
John Preston
ff14ac68ee Always show tooltip about the muted microphone. 2021-06-22 19:50:26 +04:00
John Preston
948c5d50cb Add C++/WinRT library helper to lib_base. 2021-06-22 19:47:02 +04:00
John Preston
6b520ecc05 Add overlapping markup support. 2021-06-22 19:05:27 +04:00
John Preston
bb474686eb Use NuGet package for WinRT headers generation. 2021-06-22 09:53:20 +04:00
John Preston
659ddae9a8 Use native child window in video calls on Windows. 2021-06-21 11:29:29 +04:00
John Preston
b70276912e Use native child window in group calls on Windows. 2021-06-21 09:23:10 +04:00
23rd
858c575782 Fixed Hunspell license. 2021-06-21 09:23:10 +04:00
23rd
62fe14d592 Fixed lock icon display when switching layers in one column mode. 2021-06-21 09:23:10 +04:00
Ilya Fedin
6ad037e556 Update lib_waylandshells and cmake_helpers 2021-06-20 10:39:42 +04:00
Ilya Fedin
a55b41faa1 Provide a list of shell integrations in QT_WAYLAND_SHELL_INTEGRATION 2021-06-20 10:39:42 +04:00
Ilya Fedin
a26d769304 Set QT_WAYLAND_SHELL_INTEGRATION to custom value 2021-06-19 08:16:38 +04:00
John Preston
e1120d1cb5 Optimize out most of LastUserInputTime() calls.
Fixes #16118.
2021-06-18 19:22:36 +04:00
John Preston
8897f9e46a Limit requested qualities to 4 Full / 16 Medium. 2021-06-18 18:43:13 +04:00
John Preston
7a588be54f Add a hint to unmute your microphone. 2021-06-18 17:47:07 +04:00
John Preston
1cb1f1cbc1 Add a hint to turn on the camera. 2021-06-18 16:11:32 +04:00
John Preston
5827d6ffdb Update lib_ui submodule. 2021-06-18 12:15:01 +04:00
Ilya Fedin
766bc90921 Adapt for Ui::DisableCustomScaling changes 2021-06-18 12:15:01 +04:00
23rd
eb228eb744 Removed unused methods from file click handler. 2021-06-18 09:48:39 +03:00
23rd
7c02d67665 Moved cancelUploadLayer from MainWidget to SessionController. 2021-06-18 09:39:10 +03:00
23rd
23c54896e5 Removed App::main() from file click handlers. 2021-06-18 09:20:49 +03:00
23rd
460baa54d8 Fixed switching between PiP and OverlayWidget. 2021-06-18 07:30:54 +03:00
23rd
6c56fad180 Fixed updating of parent id for file click handlers.
Fixed #16447.
2021-06-18 07:30:54 +03:00
23rd
3fd772ce17 Moved file click handlers to separated file. 2021-06-18 07:30:54 +03:00
John Preston
8834ec8bf2 Disable audio device tracking on macOS. 2021-06-17 17:15:13 +04:00
John Preston
003fb52fb9 Make 100% volume value more sticky. 2021-06-17 16:42:50 +04:00
John Preston
ec234cdc43 Improve volume slider design in group calls. 2021-06-17 16:22:51 +04:00
351 changed files with 11464 additions and 4715 deletions

View File

@@ -93,6 +93,9 @@ jobs:
DEFINE=""
if [ -n "${{ matrix.defines }}" ]; then
DEFINE="-D ${{ matrix.defines }}=ON"
if [ "${{ matrix.defines }}" == "DESKTOP_APP_DISABLE_DBUS_INTEGRATION" ]; then
DEFINE="$DEFINE -D DESKTOP_APP_DISABLE_GTK_INTEGRATION=ON"
fi
echo Define from matrix: $DEFINE
echo "ARTIFACT_NAME=Telegram_${{ matrix.defines }}" >> $GITHUB_ENV
else

6
.gitmodules vendored
View File

@@ -76,9 +76,6 @@
[submodule "Telegram/ThirdParty/hime"]
path = Telegram/ThirdParty/hime
url = https://github.com/hime-ime/hime.git
[submodule "Telegram/ThirdParty/qt5ct"]
path = Telegram/ThirdParty/qt5ct
url = https://github.com/desktop-app/qt5ct.git
[submodule "Telegram/ThirdParty/fcitx5-qt"]
path = Telegram/ThirdParty/fcitx5-qt
url = https://github.com/fcitx/fcitx5-qt.git
@@ -91,9 +88,6 @@
[submodule "Telegram/lib_webview"]
path = Telegram/lib_webview
url = https://github.com/desktop-app/lib_webview.git
[submodule "Telegram/ThirdParty/mallocng"]
path = Telegram/ThirdParty/mallocng
url = https://github.com/desktop-app/mallocng.git
[submodule "Telegram/lib_waylandshells"]
path = Telegram/lib_waylandshells
url = https://github.com/desktop-app/lib_waylandshells.git

View File

@@ -31,6 +31,7 @@ include(cmake/target_link_static_libraries.cmake)
include(cmake/target_link_frameworks.cmake)
include(cmake/init_target.cmake)
include(cmake/generate_target.cmake)
include(cmake/nuget.cmake)
include(cmake/options.cmake)

View File

@@ -59,7 +59,7 @@ Version **1.8.15** was the last that supports older systems
* xxHash ([BSD License](https://github.com/Cyan4973/xxHash/blob/dev/LICENSE))
* QR Code generator ([MIT License](https://github.com/nayuki/QR-Code-generator#license))
* CMake ([New BSD License](https://github.com/Kitware/CMake/blob/master/Copyright.txt))
* Hunspell ([GPL](https://github.com/hunspell/hunspell/blob/master/COPYING))
* Hunspell ([LGPL](https://github.com/hunspell/hunspell/blob/master/COPYING.LESSER))
## Build instructions

View File

@@ -82,17 +82,11 @@ PRIVATE
desktop-app::external_xxhash
)
if (WIN32)
target_link_libraries(Telegram
PRIVATE
desktop-app::lib_webview_winrt
)
elseif (LINUX)
if (LINUX)
target_link_libraries(Telegram
PRIVATE
desktop-app::external_glibmm
desktop-app::external_glib
desktop-app::external_mallocng
)
if (NOT DESKTOP_APP_DISABLE_DBUS_INTEGRATION)
@@ -119,6 +113,7 @@ elseif (LINUX)
endif()
if (NOT DESKTOP_APP_DISABLE_GTK_INTEGRATION)
target_link_libraries(Telegram PRIVATE rt)
find_package(PkgConfig REQUIRED)
if (DESKTOP_APP_USE_PACKAGED AND NOT DESKTOP_APP_USE_PACKAGED_LAZY)
@@ -130,7 +125,7 @@ elseif (LINUX)
target_link_libraries(Telegram PRIVATE PkgConfig::X11)
endif()
else()
pkg_search_module(GTK REQUIRED gtk+-3.0 gtk+-2.0)
pkg_check_modules(GTK REQUIRED gtk+-3.0)
target_include_directories(Telegram PRIVATE ${GTK_INCLUDE_DIRS})
if (NOT DESKTOP_APP_DISABLE_X11_INTEGRATION)
@@ -256,8 +251,6 @@ PRIVATE
boxes/peer_lists_box.h
boxes/passcode_box.cpp
boxes/passcode_box.h
boxes/photo_crop_box.cpp
boxes/photo_crop_box.h
boxes/rate_call_box.cpp
boxes/rate_call_box.h
boxes/self_destruction_box.cpp
@@ -400,6 +393,7 @@ PRIVATE
data/stickers/data_stickers_set.h
data/stickers/data_stickers.cpp
data/stickers/data_stickers.h
data/data_abstract_sparse_ids.h
data/data_abstract_structure.cpp
data/data_abstract_structure.h
data/data_auto_download.cpp
@@ -428,6 +422,8 @@ PRIVATE
data/data_drafts.h
data/data_folder.cpp
data/data_folder.h
data/data_file_click_handler.cpp
data/data_file_click_handler.h
data/data_file_origin.cpp
data/data_file_origin.h
data/data_flags.h
@@ -510,6 +506,39 @@ PRIVATE
dialogs/dialogs_search_from_controllers.h
dialogs/dialogs_widget.cpp
dialogs/dialogs_widget.h
editor/color_picker.cpp
editor/color_picker.h
editor/controllers/controllers.h
editor/controllers/stickers_panel_controller.cpp
editor/controllers/stickers_panel_controller.h
editor/controllers/undo_controller.cpp
editor/controllers/undo_controller.h
editor/editor_crop.cpp
editor/editor_crop.h
editor/editor_paint.cpp
editor/editor_paint.h
editor/photo_editor.cpp
editor/photo_editor.h
editor/photo_editor_common.cpp
editor/photo_editor_common.h
editor/photo_editor_content.cpp
editor/photo_editor_content.h
editor/photo_editor_controls.cpp
editor/photo_editor_controls.h
editor/photo_editor_layer_widget.cpp
editor/photo_editor_layer_widget.h
editor/scene/scene.cpp
editor/scene/scene.h
editor/scene/scene_item_base.cpp
editor/scene/scene_item_base.h
editor/scene/scene_item_canvas.cpp
editor/scene/scene_item_canvas.h
editor/scene/scene_item_image.cpp
editor/scene/scene_item_image.h
editor/scene/scene_item_line.cpp
editor/scene/scene_item_line.h
editor/scene/scene_item_sticker.cpp
editor/scene/scene_item_sticker.h
export/export_manager.cpp
export/export_manager.h
export/view/export_view_content.cpp
@@ -866,8 +895,6 @@ PRIVATE
platform/linux/linux_gdk_helper.h
platform/linux/linux_gsd_media_keys.cpp
platform/linux/linux_gsd_media_keys.h
platform/linux/linux_gtk_file_dialog.cpp
platform/linux/linux_gtk_file_dialog.h
platform/linux/linux_gtk_integration_dummy.cpp
platform/linux/linux_gtk_integration_p.h
platform/linux/linux_gtk_integration.cpp
@@ -1199,8 +1226,6 @@ if (DESKTOP_APP_DISABLE_GTK_INTEGRATION)
remove_target_sources(Telegram ${src_loc}
platform/linux/linux_gdk_helper.cpp
platform/linux/linux_gdk_helper.h
platform/linux/linux_gtk_file_dialog.cpp
platform/linux/linux_gtk_file_dialog.h
platform/linux/linux_gtk_integration_p.h
platform/linux/linux_gtk_integration.cpp
platform/linux/linux_gtk_open_with_dialog.cpp
@@ -1365,6 +1390,32 @@ endif()
set_target_properties(Telegram PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
if (WIN32)
target_link_options(Telegram
PRIVATE
/DELAYLOAD:secur32.dll
/DELAYLOAD:winmm.dll
/DELAYLOAD:ws2_32.dll
/DELAYLOAD:user32.dll
/DELAYLOAD:gdi32.dll
/DELAYLOAD:advapi32.dll
/DELAYLOAD:shell32.dll
/DELAYLOAD:ole32.dll
/DELAYLOAD:oleaut32.dll
/DELAYLOAD:shlwapi.dll
/DELAYLOAD:iphlpapi.dll
/DELAYLOAD:gdiplus.dll
/DELAYLOAD:version.dll
/DELAYLOAD:dwmapi.dll
/DELAYLOAD:crypt32.dll
/DELAYLOAD:bcrypt.dll
/DELAYLOAD:imm32.dll
/DELAYLOAD:netapi32.dll
/DELAYLOAD:userenv.dll
/DELAYLOAD:wtsapi32.dll
)
endif()
if ((NOT DESKTOP_APP_DISABLE_AUTOUPDATE OR APPLE) AND NOT build_macstore AND NOT build_winstore)
add_executable(Updater WIN32)
init_target(Updater)
@@ -1381,8 +1432,26 @@ if ((NOT DESKTOP_APP_DISABLE_AUTOUPDATE OR APPLE) AND NOT build_macstore AND NOT
set_target_properties(Updater PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
if (WIN32 AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
target_link_options(Updater PRIVATE -municode)
if (WIN32)
get_filename_component(lib_base_loc lib_base REALPATH)
nice_target_sources(Updater ${lib_base_loc}
PRIVATE
base/platform/win/base_windows_safe_library.cpp
base/platform/win/base_windows_safe_library.h
)
target_include_directories(Updater PRIVATE ${lib_base_loc})
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
target_link_options(Updater
PRIVATE
/DELAYLOAD:user32.dll
/DELAYLOAD:advapi32.dll
/DELAYLOAD:shell32.dll
/DELAYLOAD:ole32.dll
/DELAYLOAD:shlwapi.dll
)
else()
target_link_options(Updater PRIVATE -municode)
endif()
endif()
if (LINUX)

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 622 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 947 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 971 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -184,6 +184,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_edit_media_album_error" = "This file cannot be saved as a part of an album.";
"lng_edit_media_invalid_file" = "Sorry, no way to use this file.";
"lng_edit_photo_editor_hint" = "Left-click on the photo to edit.";
"lng_edit_caption_attach" = "Sorry, you can't attach a new media while you're editing your message.";
"lng_edit_caption_voice" = "Sorry, you can't edit your message while you're having an unsent voice message.";
@@ -452,6 +453,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_performance" = "Performance";
"lng_settings_enable_animations" = "Enable animations";
"lng_settings_enable_opengl" = "Enable OpenGL rendering for media";
"lng_settings_angle_backend" = "ANGLE graphics backend";
"lng_settings_angle_backend_auto" = "Auto";
"lng_settings_angle_backend_d3d9" = "Direct3D 9";
"lng_settings_angle_backend_d3d11" = "Direct3D 11";
"lng_settings_angle_backend_d3d11on12" = "D3D11on12";
"lng_settings_angle_backend_opengl" = "OpenGL";
"lng_settings_angle_backend_disabled" = "Disabled";
"lng_settings_sensitive_title" = "Sensitive content";
"lng_settings_sensitive_disable_filtering" = "Disable filtering";
"lng_settings_sensitive_about" = "Display sensitive media in public channels on all your Telegram devices.";
@@ -1007,6 +1015,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_report_group_title" = "Report group";
"lng_report_bot_title" = "Report bot";
"lng_report_message_title" = "Report message";
"lng_report_please_select_messages" = "Please select messages to report.";
"lng_report_select_messages" = "Select messages";
"lng_report_messages_none" = "Select Messages";
"lng_report_messages_count#one" = "Report {count} Message";
@@ -1371,6 +1380,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_switch_stickers" = "Stickers";
"lng_switch_emoji" = "Emoji";
"lng_switch_gifs" = "GIFs";
"lng_switch_masks" = "Masks";
"lng_stickers_featured_add" = "Add";
"lng_gifs_search" = "Search GIFs";
"lng_gifs_no_saved" = "You have no saved GIFs yet.";
@@ -1381,11 +1391,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_box_remove" = "Remove";
"lng_stickers_installed_tab" = "Stickers";
"lng_stickers_masks_tab" = "Masks";
"lng_stickers_featured_tab" = "Trending";
"lng_stickers_archived_tab" = "Archived";
"lng_stickers_remove_pack" = "Remove «{sticker_pack}»?";
"lng_stickers_add_pack" = "Add stickers";
"lng_stickers_add_masks" = "Add masks";
"lng_stickers_share_pack" = "Share Stickers";
"lng_stickers_share_masks" = "Share Masks";
"lng_stickers_not_found" = "Sticker pack not found.";
"lng_stickers_packs_archived" = "Some of your unused stickers have been archived to make room for the sets you've activated.";
"lng_stickers_copied" = "Sticker pack link copied to clipboard.";
@@ -1394,7 +1407,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_stickers_return" = "Undo";
"lng_stickers_count#one" = "{count} sticker";
"lng_stickers_count#other" = "{count} stickers";
"lng_stickers_masks_pack" = "This is a pack of mask stickers. You can use them in the photo editor on our mobile apps.";
"lng_masks_count#one" = "{count} mask";
"lng_masks_count#other" = "{count} masks";
"lng_stickers_attached_sets" = "Sets of attached stickers";
"lng_stickers_group_set" = "Group sticker set";
"lng_stickers_remove_group_set" = "Remove group sticker set?";
@@ -1405,6 +1419,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_stickers_remove_pack_confirm" = "Remove";
"lng_stickers_archive_pack" = "Archive Stickers";
"lng_stickers_has_been_archived" = "Sticker pack has been archived.";
"lng_masks_archive_pack" = "Archive Masks";
"lng_masks_has_been_archived" = "Mask pack has been archived.";
"lng_masks_installed" = "Mask pack has been installed.";
"lng_in_dlg_photo" = "Photo";
"lng_in_dlg_album" = "Album";
@@ -2030,6 +2047,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_call_chat_no_camera" = "You can't turn on video in this chat.";
"lng_group_call_chat_no_screen" = "You can't share your screen in this chat.";
"lng_group_call_failed_screen" = "An error occured. Screencast has stopped.";
"lng_group_call_failed_camera" = "Could not enable camera. Perhaps another app is using the camera already. Try closing other apps.";
"lng_group_call_tooltip_screen" = "Share screen";
"lng_group_call_tooltip_camera" = "Your camera is off. Click here to enable camera.";
"lng_group_call_tooltip_microphone" = "You are on mute. Click here to speak.";
@@ -2758,6 +2776,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_filters_remove_sure" = "This will remove the folder, your chats will not be deleted.";
"lng_filters_remove_yes" = "Remove";
"lng_photo_editor_menu_delete" = "Delete";
"lng_photo_editor_menu_flip" = "Flip";
"lng_photo_editor_menu_duplicate" = "Duplicate";
// Wnd specific
"lng_wnd_choose_program_menu" = "Choose Default Program...";

View File

@@ -579,8 +579,8 @@ keyboardButtonRequestPoll#bbc7515d flags:# quiz:flags.0?Bool text:string = Keybo
keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;
replyKeyboardHide#a03e5b85 flags:# selective:flags.2?true = ReplyMarkup;
replyKeyboardForceReply#f4108aa0 flags:# single_use:flags.1?true selective:flags.2?true = ReplyMarkup;
replyKeyboardMarkup#3502758c flags:# resize:flags.0?true single_use:flags.1?true selective:flags.2?true rows:Vector<KeyboardButtonRow> = ReplyMarkup;
replyKeyboardForceReply#86b40b08 flags:# single_use:flags.1?true selective:flags.2?true placeholder:flags.3?string = ReplyMarkup;
replyKeyboardMarkup#85dd99d1 flags:# resize:flags.0?true single_use:flags.1?true selective:flags.2?true rows:Vector<KeyboardButtonRow> placeholder:flags.3?string = ReplyMarkup;
replyInlineMarkup#48a30254 rows:Vector<KeyboardButtonRow> = ReplyMarkup;
messageEntityUnknown#bb92ba95 offset:int length:int = MessageEntity;
@@ -1250,6 +1250,16 @@ groupCallParticipantVideoSourceGroup#dcb118b7 semantics:string sources:Vector<in
groupCallParticipantVideo#78e41663 flags:# paused:flags.0?true endpoint:string source_groups:Vector<GroupCallParticipantVideoSourceGroup> = GroupCallParticipantVideo;
stickers.suggestedShortName#85fea03f short_name:string = stickers.SuggestedShortName;
botCommandScopeDefault#2f6cb2ab = BotCommandScope;
botCommandScopeUsers#3c4f04d8 = BotCommandScope;
botCommandScopeChats#6fe1a881 = BotCommandScope;
botCommandScopeChatAdmins#b9aa606a = BotCommandScope;
botCommandScopePeer#db9d897d peer:InputPeer = BotCommandScope;
botCommandScopePeerAdmins#3fd863d1 peer:InputPeer = BotCommandScope;
botCommandScopePeerUser#a1321f3 peer:InputPeer user_id:InputUser = BotCommandScope;
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@@ -1596,7 +1606,9 @@ channels.convertToGigagroup#b290c69 channel:InputChannel = Updates;
bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON;
bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool;
bots.setBotCommands#805d46f6 commands:Vector<BotCommand> = Bool;
bots.setBotCommands#517165a scope:BotCommandScope lang_code:string commands:Vector<BotCommand> = Bool;
bots.resetBotCommands#3d8de0f9 scope:BotCommandScope lang_code:string = Bool;
bots.getBotCommands#e34c0dd6 scope:BotCommandScope lang_code:string = Vector<BotCommand>;
payments.getPaymentForm#8a333c8d flags:# peer:InputPeer msg_id:int theme_params:flags.0?DataJSON = payments.PaymentForm;
payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt;
@@ -1606,11 +1618,13 @@ payments.getSavedInfo#227d824b = payments.SavedInfo;
payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool;
payments.getBankCardData#2e79d779 number:string = payments.BankCardData;
stickers.createStickerSet#f1036780 flags:# masks:flags.0?true animated:flags.1?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector<InputStickerSetItem> = messages.StickerSet;
stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector<InputStickerSetItem> software:flags.3?string = messages.StickerSet;
stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet;
stickers.changeStickerPosition#ffb6d4ca sticker:InputDocument position:int = messages.StickerSet;
stickers.addStickerToSet#8653febe stickerset:InputStickerSet sticker:InputStickerSetItem = messages.StickerSet;
stickers.setStickerSetThumb#9a364e30 stickerset:InputStickerSet thumb:InputDocument = messages.StickerSet;
stickers.checkShortName#284b3639 short_name:string = Bool;
stickers.suggestShortName#4dafc503 title:string = stickers.SuggestedShortName;
phone.getCallConfig#55451fa9 = DataJSON;
phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
@@ -1656,4 +1670,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
// LAYER 129
// LAYER 130

View File

@@ -9,7 +9,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="2.7.9.0" />
Version="2.8.6.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View File

@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,7,9,0
PRODUCTVERSION 2,7,9,0
FILEVERSION 2,8,6,0
PRODUCTVERSION 2,8,6,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "2.7.9.0"
VALUE "FileVersion", "2.8.6.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.7.9.0"
VALUE "ProductVersion", "2.8.6.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,7,9,0
PRODUCTVERSION 2,7,9,0
FILEVERSION 2,8,6,0
PRODUCTVERSION 2,8,6,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop Updater"
VALUE "FileVersion", "2.7.9.0"
VALUE "FileVersion", "2.8.6.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.7.9.0"
VALUE "ProductVersion", "2.8.6.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "updater.h"
#include "base/platform/win/base_windows_safe_library.h"
bool _debug = false;
wstring updaterName, updaterDir, updateTo, exeName, customWorkingDir, customKeyFile;
@@ -329,6 +331,8 @@ void updateRegistry() {
}
int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdParamarg, int cmdShow) {
base::Platform::InitDynamicLibraries();
openLog();
_oldWndExceptionFilter = SetUnhandledExceptionFilter(_exceptionFilter);

View File

@@ -52,6 +52,10 @@ mtpRequestId EditMessage(
ConvertOption::SkipLocal);
const auto media = item->media();
const auto updateRecentStickers = inputMedia.has_value()
? Api::HasAttachedStickers(*inputMedia)
: false;
const auto emptyFlag = MTPmessages_EditMessage::Flag(0);
const auto flags = emptyFlag
| (!text.isEmpty() || media
@@ -97,6 +101,10 @@ mtpRequestId EditMessage(
} else {
apply();
}
if (updateRecentStickers) {
api->requestRecentStickersForce(true);
}
}).fail(
fail
).send();
@@ -165,22 +173,30 @@ void EditMessageWithUploadedDocument(
HistoryItem *item,
const MTPInputFile &file,
const std::optional<MTPInputFile> &thumb,
SendOptions options) {
SendOptions options,
std::vector<MTPInputDocument> attachedStickers) {
if (!item || !item->media() || !item->media()->document()) {
return;
}
const auto media = PrepareUploadedDocument(item, file, thumb);
const auto media = PrepareUploadedDocument(
item,
file,
thumb,
std::move(attachedStickers));
EditMessageWithUploadedMedia(item, options, media);
}
void EditMessageWithUploadedPhoto(
HistoryItem *item,
const MTPInputFile &file,
SendOptions options) {
SendOptions options,
std::vector<MTPInputDocument> attachedStickers) {
if (!item || !item->media() || !item->media()->photo()) {
return;
}
const auto media = PrepareUploadedPhoto(file);
const auto media = PrepareUploadedPhoto(
file,
std::move(attachedStickers));
EditMessageWithUploadedMedia(item, options, media);
}

View File

@@ -31,12 +31,14 @@ void EditMessageWithUploadedDocument(
HistoryItem *item,
const MTPInputFile &file,
const std::optional<MTPInputFile> &thumb,
SendOptions options);
SendOptions options,
std::vector<MTPInputDocument> attachedStickers);
void EditMessageWithUploadedPhoto(
HistoryItem *item,
const MTPInputFile &file,
SendOptions options);
SendOptions options,
std::vector<MTPInputDocument> attachedStickers);
mtpRequestId EditCaption(
not_null<HistoryItem*> item,

View File

@@ -61,10 +61,14 @@ int32 CountStickersHash(
: 0;
}
int32 CountRecentStickersHash(not_null<Main::Session*> session) {
int32 CountRecentStickersHash(
not_null<Main::Session*> session,
bool attached) {
return CountSpecialStickerSetHash(
session,
Data::Stickers::CloudRecentSetId);
attached
? Data::Stickers::CloudRecentAttachedSetId
: Data::Stickers::CloudRecentSetId);
}
int32 CountFavedStickersHash(not_null<Main::Session*> session) {

View File

@@ -17,7 +17,8 @@ namespace Api {
not_null<Main::Session*> session,
bool checkOutdatedInfo = false);
[[nodiscard]] int32 CountRecentStickersHash(
not_null<Main::Session*> session);
not_null<Main::Session*> session,
bool attached = false);
[[nodiscard]] int32 CountFavedStickersHash(not_null<Main::Session*> session);
[[nodiscard]] int32 CountFeaturedStickersHash(
not_null<Main::Session*> session);

View File

@@ -73,18 +73,24 @@ MTPVector<MTPDocumentAttribute> ComposeSendingDocumentAttributes(
} // namespace
MTPInputMedia PrepareUploadedPhoto(const MTPInputFile &file) {
MTPInputMedia PrepareUploadedPhoto(
const MTPInputFile &file,
std::vector<MTPInputDocument> attachedStickers) {
const auto flags = attachedStickers.empty()
? MTPDinputMediaUploadedPhoto::Flags(0)
: MTPDinputMediaUploadedPhoto::Flag::f_stickers;
return MTP_inputMediaUploadedPhoto(
MTP_flags(0),
MTP_flags(flags),
file,
MTPVector<MTPInputDocument>(),
MTP_vector<MTPInputDocument>(ranges::to<QVector>(attachedStickers)),
MTP_int(0));
}
MTPInputMedia PrepareUploadedDocument(
not_null<HistoryItem*> item,
const MTPInputFile &file,
const std::optional<MTPInputFile> &thumb) {
const std::optional<MTPInputFile> &thumb,
std::vector<MTPInputDocument> attachedStickers) {
if (!item || !item->media() || !item->media()->document()) {
return MTP_inputMediaEmpty();
}
@@ -92,7 +98,8 @@ MTPInputMedia PrepareUploadedDocument(
using DocFlags = MTPDinputMediaUploadedDocument::Flag;
const auto flags = emptyFlag
| (thumb ? DocFlags::f_thumb : emptyFlag)
| (item->groupId() ? DocFlags::f_nosound_video : emptyFlag);
| (item->groupId() ? DocFlags::f_nosound_video : emptyFlag)
| (attachedStickers.empty() ? DocFlags::f_stickers : emptyFlag);
const auto document = item->media()->document();
return MTP_inputMediaUploadedDocument(
MTP_flags(flags),
@@ -100,8 +107,20 @@ MTPInputMedia PrepareUploadedDocument(
thumb.value_or(MTPInputFile()),
MTP_string(document->mimeString()),
ComposeSendingDocumentAttributes(document),
MTPVector<MTPInputDocument>(),
MTP_vector<MTPInputDocument>(ranges::to<QVector>(attachedStickers)),
MTP_int(0));
}
bool HasAttachedStickers(MTPInputMedia media) {
return media.match([&](const MTPDinputMediaUploadedPhoto &photo) -> bool {
return (photo.vflags().v
& MTPDinputMediaUploadedPhoto::Flag::f_stickers);
}, [&](const MTPDinputMediaUploadedDocument &document) -> bool {
return (document.vflags().v
& MTPDinputMediaUploadedDocument::Flag::f_stickers);
}, [](const auto &d) {
return false;
});
}
} // namespace Api

View File

@@ -11,11 +11,16 @@ class HistoryItem;
namespace Api {
MTPInputMedia PrepareUploadedPhoto(const MTPInputFile &file);
MTPInputMedia PrepareUploadedPhoto(
const MTPInputFile &file,
std::vector<MTPInputDocument> attachedStickers);
MTPInputMedia PrepareUploadedDocument(
not_null<HistoryItem*> item,
const MTPInputFile &file,
const std::optional<MTPInputFile> &thumb);
const std::optional<MTPInputFile> &thumb,
std::vector<MTPInputDocument> attachedStickers);
bool HasAttachedStickers(MTPInputMedia media);
} // namespace Api

View File

@@ -862,8 +862,8 @@ int32 Updates::pts() const {
return _ptsWaiter.current();
}
void Updates::updateOnline() {
updateOnline(false);
void Updates::updateOnline(crl::time lastNonIdleTime) {
updateOnline(lastNonIdleTime, false);
}
bool Updates::isIdle() const {
@@ -874,15 +874,20 @@ rpl::producer<bool> Updates::isIdleValue() const {
return _isIdle.value();
}
void Updates::updateOnline(bool gotOtherOffline) {
crl::on_main(&session(), [] { Core::App().checkAutoLock(); });
void Updates::updateOnline(crl::time lastNonIdleTime, bool gotOtherOffline) {
if (!lastNonIdleTime) {
lastNonIdleTime = Core::App().lastNonIdleTime();
}
crl::on_main(&session(), [=] {
Core::App().checkAutoLock(lastNonIdleTime);
});
const auto &config = _session->serverConfig();
bool isOnline = Core::App().hasActiveWindow(&session());
int updateIn = config.onlineUpdatePeriod;
Assert(updateIn >= 0);
if (isOnline) {
const auto idle = crl::now() - Core::App().lastNonIdleTime();
const auto idle = crl::now() - lastNonIdleTime;
if (idle >= config.offlineIdleTimeout) {
isOnline = false;
if (!isIdle()) {
@@ -933,10 +938,13 @@ void Updates::updateOnline(bool gotOtherOffline) {
_onlineTimer.callOnce(updateIn);
}
void Updates::checkIdleFinish() {
if (crl::now() - Core::App().lastNonIdleTime()
void Updates::checkIdleFinish(crl::time lastNonIdleTime) {
if (!lastNonIdleTime) {
lastNonIdleTime = Core::App().lastNonIdleTime();
}
if (crl::now() - lastNonIdleTime
< _session->serverConfig().offlineIdleTimeout) {
updateOnline();
updateOnline(lastNonIdleTime);
_idleFinishTimer.cancel();
_isIdle = false;
} else {
@@ -957,9 +965,10 @@ bool Updates::isQuitPrevent() {
return false;
}
LOG(("Api::Updates prevents quit, sending offline status..."));
updateOnline();
updateOnline(crl::now());
return true;
}
void Updates::handleSendActionUpdate(
PeerId peerId,
MsgId rootId,
@@ -1750,7 +1759,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
if (UserId(d.vuser_id()) == session().userId()) {
if (d.vstatus().type() == mtpc_userStatusOffline
|| d.vstatus().type() == mtpc_userStatusEmpty) {
updateOnline(true);
updateOnline(Core::App().lastNonIdleTime(), true);
if (d.vstatus().type() == mtpc_userStatusOffline) {
cSetOtherOnline(
d.vstatus().c_userStatusOffline().vwas_online().v);
@@ -2132,31 +2141,46 @@ void Updates::feedUpdate(const MTPUpdate &update) {
case mtpc_updateStickerSetsOrder: {
auto &d = update.c_updateStickerSetsOrder();
if (!d.is_masks()) {
const auto &order = d.vorder().v;
const auto &sets = session().data().stickers().sets();
Data::StickersSetsOrder result;
for (const auto &item : order) {
if (sets.find(item.v) == sets.cend()) {
break;
}
result.push_back(item.v);
auto &stickers = session().data().stickers();
const auto isMasks = d.is_masks();
const auto &order = d.vorder().v;
const auto &sets = stickers.sets();
Data::StickersSetsOrder result;
for (const auto &item : order) {
if (sets.find(item.v) == sets.cend()) {
break;
}
if (result.size() != session().data().stickers().setsOrder().size()
|| result.size() != order.size()) {
session().data().stickers().setLastUpdate(0);
session().api().updateStickers();
result.push_back(item.v);
}
const auto localSize = isMasks
? stickers.maskSetsOrder().size()
: stickers.setsOrder().size();
if ((result.size() != localSize) || (result.size() != order.size())) {
if (isMasks) {
stickers.setLastMasksUpdate(0);
session().api().updateMasks();
} else {
session().data().stickers().setsOrderRef() = std::move(result);
session().local().writeInstalledStickers();
session().data().stickers().notifyUpdated();
stickers.setLastUpdate(0);
session().api().updateStickers();
}
} else {
if (isMasks) {
stickers.maskSetsOrderRef() = std::move(result);
session().local().writeInstalledMasks();
} else {
stickers.setsOrderRef() = std::move(result);
session().local().writeInstalledStickers();
}
stickers.notifyUpdated();
}
} break;
case mtpc_updateStickerSets: {
// Can't determine is it masks or stickers, so update both.
session().data().stickers().setLastUpdate(0);
session().api().updateStickers();
session().data().stickers().setLastMasksUpdate(0);
session().api().updateMasks();
} break;
case mtpc_updateRecentStickers: {

View File

@@ -38,10 +38,10 @@ public:
[[nodiscard]] int32 pts() const;
void updateOnline();
void updateOnline(crl::time lastNonIdleTime = 0);
[[nodiscard]] bool isIdle() const;
[[nodiscard]] rpl::producer<bool> isIdleValue() const;
void checkIdleFinish();
void checkIdleFinish(crl::time lastNonIdleTime = 0);
bool lastWasOnline() const;
crl::time lastSetOnline() const;
bool isQuitPrevent();
@@ -87,7 +87,7 @@ private:
MsgRange range,
const MTPupdates_ChannelDifference &result);
void updateOnline(bool gotOtherOffline);
void updateOnline(crl::time lastNonIdleTime, bool gotOtherOffline);
void sendPing();
void getDifferenceByPts();
void getDifferenceAfterFail();

View File

@@ -1857,9 +1857,14 @@ void ApiWrap::requestStickerSets() {
if (i.value().second) continue;
auto waitMs = (j == e) ? 0 : kSmallDelayMs;
i.value().second = request(MTPmessages_GetStickerSet(MTP_inputStickerSetID(MTP_long(i.key()), MTP_long(i.value().first)))).done([this, setId = i.key()](const MTPmessages_StickerSet &result) {
const auto id = MTP_inputStickerSetID(
MTP_long(i.key()),
MTP_long(i.value().first));
i.value().second = request(MTPmessages_GetStickerSet(
id
)).done([=, setId = i.key()](const MTPmessages_StickerSet &result) {
gotStickerSet(setId, result);
}).fail([this, setId = i.key()](const MTP::Error &error) {
}).fail([=, setId = i.key()](const MTP::Error &error) {
_stickerSetRequests.remove(setId);
}).afterDelay(waitMs).send();
}
@@ -1867,23 +1872,91 @@ void ApiWrap::requestStickerSets() {
void ApiWrap::saveStickerSets(
const Data::StickersSetsOrder &localOrder,
const Data::StickersSetsOrder &localRemoved) {
for (auto requestId : base::take(_stickerSetDisenableRequests)) {
const Data::StickersSetsOrder &localRemoved,
bool setsMasks) {
auto &setDisenableRequests = setsMasks
? _maskSetDisenableRequests
: _stickerSetDisenableRequests;
const auto reorderRequestId = [=]() -> mtpRequestId & {
return setsMasks
? _masksReorderRequestId
: _stickersReorderRequestId;
};
for (auto requestId : base::take(setDisenableRequests)) {
request(requestId).cancel();
}
request(base::take(_stickersReorderRequestId)).cancel();
request(base::take(reorderRequestId())).cancel();
request(base::take(_stickersClearRecentRequestId)).cancel();
request(base::take(_stickersClearRecentAttachedRequestId)).cancel();
auto writeInstalled = true, writeRecent = false, writeCloudRecent = false, writeFaved = false, writeArchived = false;
const auto stickersSaveOrder = [=] {
if (localOrder.size() < 2) {
return;
}
QVector<MTPlong> mtpOrder;
mtpOrder.reserve(localOrder.size());
for (const auto setId : std::as_const(localOrder)) {
mtpOrder.push_back(MTP_long(setId));
}
const auto flags = setsMasks
? MTPmessages_ReorderStickerSets::Flag::f_masks
: MTPmessages_ReorderStickerSets::Flags(0);
reorderRequestId() = request(MTPmessages_ReorderStickerSets(
MTP_flags(flags),
MTP_vector<MTPlong>(mtpOrder)
)).done([=](const MTPBool &result) {
reorderRequestId() = 0;
}).fail([=](const MTP::Error &error) {
reorderRequestId() = 0;
if (setsMasks) {
_session->data().stickers().setLastMasksUpdate(0);
updateMasks();
} else {
_session->data().stickers().setLastUpdate(0);
updateStickers();
}
}).send();
};
const auto stickerSetDisenabled = [=](mtpRequestId requestId) {
auto &setDisenableRequests = setsMasks
? _maskSetDisenableRequests
: _stickerSetDisenableRequests;
setDisenableRequests.remove(requestId);
if (setDisenableRequests.empty()) {
stickersSaveOrder();
}
};
auto writeInstalled = true,
writeRecent = false,
writeCloudRecent = false,
writeCloudRecentAttached = false,
writeFaved = false,
writeArchived = false;
auto &recent = _session->data().stickers().getRecentPack();
auto &sets = _session->data().stickers().setsRef();
_stickersOrder = localOrder;
auto &order = setsMasks
? _session->data().stickers().maskSetsOrder()
: _session->data().stickers().setsOrder();
auto &orderRef = setsMasks
? _session->data().stickers().maskSetsOrderRef()
: _session->data().stickers().setsOrderRef();
using Flag = MTPDstickerSet::Flag;
using ClientFlag = MTPDstickerSet_ClientFlag;
for (const auto removedSetId : localRemoved) {
if (removedSetId == Data::Stickers::CloudRecentSetId) {
if ((removedSetId == Data::Stickers::CloudRecentSetId)
|| (removedSetId == Data::Stickers::CloudRecentAttachedSetId)) {
if (sets.remove(Data::Stickers::CloudRecentSetId) != 0) {
writeCloudRecent = true;
}
if (sets.remove(Data::Stickers::CloudRecentAttachedSetId) != 0) {
writeCloudRecentAttached = true;
}
if (sets.remove(Data::Stickers::CustomSetId)) {
writeInstalled = true;
}
@@ -1892,12 +1965,25 @@ void ApiWrap::saveStickerSets(
writeRecent = true;
}
_stickersClearRecentRequestId = request(MTPmessages_ClearRecentStickers(
MTP_flags(0)
)).done([this](const MTPBool &result) {
_stickersClearRecentRequestId = 0;
}).fail([this](const MTP::Error &error) {
_stickersClearRecentRequestId = 0;
const auto isAttached =
(removedSetId == Data::Stickers::CloudRecentAttachedSetId);
const auto flags = isAttached
? MTPmessages_ClearRecentStickers::Flag::f_attached
: MTPmessages_ClearRecentStickers::Flags(0);
auto &requestId = isAttached
? _stickersClearRecentAttachedRequestId
: _stickersClearRecentRequestId;
const auto finish = [=] {
(isAttached
? _stickersClearRecentAttachedRequestId
: _stickersClearRecentRequestId) = 0;
};
requestId = request(MTPmessages_ClearRecentStickers(
MTP_flags(flags)
)).done([=](const MTPBool &result) {
finish();
}).fail([=](const MTP::Error &error) {
finish();
}).send();
continue;
}
@@ -1913,27 +1999,34 @@ void ApiWrap::saveStickerSets(
++i;
}
}
if (!(set->flags & MTPDstickerSet::Flag::f_archived)) {
const auto archived = !!(set->flags & Flag::f_archived);
if (!archived) {
const auto featured = !!(set->flags & ClientFlag::f_featured);
const auto special = !!(set->flags & ClientFlag::f_special);
const auto setId = set->mtpInput();
auto requestId = request(MTPmessages_UninstallStickerSet(setId)).done([this](const MTPBool &result, mtpRequestId requestId) {
auto requestId = request(MTPmessages_UninstallStickerSet(
setId
)).done([=](const MTPBool &result, mtpRequestId requestId) {
stickerSetDisenabled(requestId);
}).fail([this](const MTP::Error &error, mtpRequestId requestId) {
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
stickerSetDisenabled(requestId);
}).afterDelay(kSmallDelayMs).send();
_stickerSetDisenableRequests.insert(requestId);
setDisenableRequests.insert(requestId);
int removeIndex = _session->data().stickers().setsOrder().indexOf(set->id);
if (removeIndex >= 0) _session->data().stickers().setsOrderRef().removeAt(removeIndex);
if (!(set->flags & MTPDstickerSet_ClientFlag::f_featured)
&& !(set->flags & MTPDstickerSet_ClientFlag::f_special)) {
const auto removeIndex = order.indexOf(set->id);
if (removeIndex >= 0) {
orderRef.removeAt(removeIndex);
}
if (!featured && !special) {
sets.erase(it);
} else {
if (set->flags & MTPDstickerSet::Flag::f_archived) {
if (archived) {
writeArchived = true;
}
set->flags &= ~(MTPDstickerSet::Flag::f_installed_date | MTPDstickerSet::Flag::f_archived);
set->flags &= ~(Flag::f_installed_date
| Flag::f_archived);
set->installDate = TimeId(0);
}
}
@@ -1942,51 +2035,55 @@ void ApiWrap::saveStickerSets(
// Clear all installed flags, set only for sets from order.
for (auto &[id, set] : sets) {
if (!(set->flags & MTPDstickerSet::Flag::f_archived)) {
set->flags &= ~MTPDstickerSet::Flag::f_installed_date;
const auto archived = !!(set->flags & Flag::f_archived);
const auto masks = !!(set->flags & MTPDstickerSet::Flag::f_masks);
if (!archived && (setsMasks == masks)) {
set->flags &= ~Flag::f_installed_date;
}
}
auto &order = _session->data().stickers().setsOrderRef();
order.clear();
for (const auto setId : std::as_const(_stickersOrder)) {
orderRef.clear();
for (const auto setId : std::as_const(localOrder)) {
auto it = sets.find(setId);
if (it != sets.cend()) {
const auto set = it->second.get();
if ((set->flags & MTPDstickerSet::Flag::f_archived) && !localRemoved.contains(set->id)) {
const auto mtpSetId = set->mtpInput();
if (it == sets.cend()) {
continue;
}
const auto set = it->second.get();
const auto archived = !!(set->flags & Flag::f_archived);
if (archived && !localRemoved.contains(set->id)) {
const auto mtpSetId = set->mtpInput();
const auto requestId = request(MTPmessages_InstallStickerSet(
mtpSetId,
MTP_boolFalse()
)).done([=](
const MTPmessages_StickerSetInstallResult &result,
mtpRequestId requestId) {
stickerSetDisenabled(requestId);
}).fail([=](
const MTP::Error &error,
mtpRequestId requestId) {
stickerSetDisenabled(requestId);
}).afterDelay(kSmallDelayMs).send();
const auto requestId = request(MTPmessages_InstallStickerSet(
mtpSetId,
MTP_boolFalse()
)).done([=](
const MTPmessages_StickerSetInstallResult &result,
mtpRequestId requestId) {
stickerSetDisenabled(requestId);
}).fail([=](
const MTP::Error &error,
mtpRequestId requestId) {
stickerSetDisenabled(requestId);
}).afterDelay(kSmallDelayMs).send();
_stickerSetDisenableRequests.insert(requestId);
setDisenableRequests.insert(requestId);
set->flags &= ~MTPDstickerSet::Flag::f_archived;
writeArchived = true;
}
order.push_back(setId);
set->flags |= MTPDstickerSet::Flag::f_installed_date;
if (!set->installDate) {
set->installDate = base::unixtime::now();
}
set->flags &= ~Flag::f_archived;
writeArchived = true;
}
orderRef.push_back(setId);
set->flags |= Flag::f_installed_date;
if (!set->installDate) {
set->installDate = base::unixtime::now();
}
}
for (auto it = sets.begin(); it != sets.cend();) {
const auto set = it->second.get();
if ((set->flags & MTPDstickerSet_ClientFlag::f_featured)
|| (set->flags & MTPDstickerSet::Flag::f_installed_date)
|| (set->flags & MTPDstickerSet::Flag::f_archived)
|| (set->flags & MTPDstickerSet_ClientFlag::f_special)) {
if ((set->flags & ClientFlag::f_featured)
|| (set->flags & Flag::f_installed_date)
|| (set->flags & Flag::f_archived)
|| (set->flags & ClientFlag::f_special)) {
++it;
} else {
it = sets.erase(it);
@@ -1994,27 +2091,40 @@ void ApiWrap::saveStickerSets(
}
auto &storage = local();
if (writeInstalled) storage.writeInstalledStickers();
if (writeRecent) session().saveSettings();
if (writeArchived) storage.writeArchivedStickers();
if (writeCloudRecent) storage.writeRecentStickers();
if (writeFaved) storage.writeFavedStickers();
if (writeInstalled && !setsMasks) {
storage.writeInstalledStickers();
}
if (writeInstalled && setsMasks) {
storage.writeInstalledMasks();
}
if (writeRecent) {
session().saveSettings();
}
if (writeArchived) {
if (setsMasks) {
storage.writeArchivedMasks();
} else {
storage.writeArchivedStickers();
}
}
if (writeCloudRecent) {
storage.writeRecentStickers();
}
if (writeCloudRecentAttached) {
storage.writeRecentMasks();
}
if (writeFaved) {
storage.writeFavedStickers();
}
_session->data().stickers().notifyUpdated();
if (_stickerSetDisenableRequests.empty()) {
if (setDisenableRequests.empty()) {
stickersSaveOrder();
} else {
requestSendDelayed();
}
}
void ApiWrap::stickerSetDisenabled(mtpRequestId requestId) {
_stickerSetDisenableRequests.remove(requestId);
if (_stickerSetDisenableRequests.empty()) {
stickersSaveOrder();
}
};
void ApiWrap::joinChannel(not_null<ChannelData*> channel) {
if (channel->amIn()) {
session().changes().peerUpdated(
@@ -2825,12 +2935,24 @@ void ApiWrap::refreshFileReference(
}, [&](Data::FileOriginPeerPhoto data) {
fail();
}, [&](Data::FileOriginStickerSet data) {
const auto isRecentAttached =
(data.setId == Data::Stickers::CloudRecentAttachedSetId);
if (data.setId == Data::Stickers::CloudRecentSetId
|| data.setId == Data::Stickers::RecentSetId) {
|| data.setId == Data::Stickers::RecentSetId
|| isRecentAttached) {
auto done = [=] { crl::on_main(_session, [=] {
if (isRecentAttached) {
local().writeRecentMasks();
} else {
local().writeRecentStickers();
}
}); };
request(MTPmessages_GetRecentStickers(
MTP_flags(0),
MTP_flags(isRecentAttached
? MTPmessages_GetRecentStickers::Flag::f_attached
: MTPmessages_GetRecentStickers::Flags(0)),
MTP_int(0)),
[=] { crl::on_main(_session, [=] { local().writeRecentStickers(); }); });
std::move(done));
} else if (data.setId == Data::Stickers::FavedSetId) {
request(MTPmessages_GetFavedStickers(MTP_int(0)),
[=] { crl::on_main(_session, [=] { local().writeFavedStickers(); }); });
@@ -2886,30 +3008,8 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &resu
_session->data().sendWebPageGamePollNotifications();
}
void ApiWrap::stickersSaveOrder() {
if (_stickersOrder.size() < 2) {
return;
}
QVector<MTPlong> mtpOrder;
mtpOrder.reserve(_stickersOrder.size());
for (const auto setId : std::as_const(_stickersOrder)) {
mtpOrder.push_back(MTP_long(setId));
}
_stickersReorderRequestId = request(MTPmessages_ReorderStickerSets(
MTP_flags(0),
MTP_vector<MTPlong>(mtpOrder)
)).done([=](const MTPBool &result) {
_stickersReorderRequestId = 0;
}).fail([=](const MTP::Error &error) {
_stickersReorderRequestId = 0;
_session->data().stickers().setLastUpdate(0);
updateStickers();
}).send();
}
void ApiWrap::updateStickers() {
auto now = crl::now();
const auto now = crl::now();
requestStickers(now);
requestRecentStickers(now);
requestFavedStickers(now);
@@ -2917,8 +3017,14 @@ void ApiWrap::updateStickers() {
requestSavedGifs(now);
}
void ApiWrap::requestRecentStickersForce() {
requestRecentStickersWithHash(0);
void ApiWrap::updateMasks() {
const auto now = crl::now();
requestStickers(now, true);
requestRecentStickers(now, true);
}
void ApiWrap::requestRecentStickersForce(bool attached) {
requestRecentStickersWithHash(0, attached);
}
void ApiWrap::setGroupStickerSet(not_null<ChannelData*> megagroup, const MTPInputStickerSet &set) {
@@ -2977,17 +3083,29 @@ std::vector<not_null<DocumentData*>> *ApiWrap::stickersByEmoji(
return nullptr;
}
void ApiWrap::requestStickers(TimeId now) {
if (!_session->data().stickers().updateNeeded(now)
|| _stickersUpdateRequest) {
void ApiWrap::requestStickers(TimeId now, bool masks) {
const auto requestId = [=]() -> mtpRequestId & {
return masks
? _masksUpdateRequest
: _stickersUpdateRequest;
};
const auto needed = masks
? _session->data().stickers().masksUpdateNeeded(now)
: _session->data().stickers().updateNeeded(now);
if (!needed || requestId()) {
return;
}
auto onDone = [this](const MTPmessages_AllStickers &result) {
_session->data().stickers().setLastUpdate(crl::now());
_stickersUpdateRequest = 0;
const auto onDone = [=](const MTPmessages_AllStickers &result) {
if (masks) {
_session->data().stickers().setLastMasksUpdate(crl::now());
} else {
_session->data().stickers().setLastUpdate(crl::now());
}
requestId() = 0;
switch (result.type()) {
case mtpc_messages_allStickersNotModified: return;
case mtpc_messages_getMaskStickers:
case mtpc_messages_allStickers: {
auto &d = result.c_messages_allStickers();
_session->data().stickers().setsReceived(
@@ -2997,39 +3115,67 @@ void ApiWrap::requestStickers(TimeId now) {
default: Unexpected("Type in ApiWrap::stickersDone()");
}
};
_stickersUpdateRequest = request(MTPmessages_GetAllStickers(
MTP_int(Api::CountStickersHash(_session, true))
)).done(onDone).fail([=](const MTP::Error &error) {
LOG(("App Fail: Failed to get stickers!"));
const auto onFail = [=](const MTP::Error &error) {
LOG(("App Fail: Failed to get %1!"
).arg(masks ? "masks" : "stickers"));
onDone(MTP_messages_allStickersNotModified());
}).send();
};
auto hash = MTP_int(Api::CountStickersHash(_session, true));
requestId() = masks
? request(MTPmessages_GetMaskStickers(
std::move(hash))
).done(onDone).fail(onFail).send()
: request(MTPmessages_GetAllStickers(
std::move(hash))
).done(onDone).fail(onFail).send();
}
void ApiWrap::requestRecentStickers(TimeId now) {
if (!_session->data().stickers().recentUpdateNeeded(now)) {
void ApiWrap::requestRecentStickers(TimeId now, bool attached) {
const auto needed = attached
? _session->data().stickers().recentAttachedUpdateNeeded(now)
: _session->data().stickers().recentUpdateNeeded(now);
if (!needed) {
return;
}
requestRecentStickersWithHash(
Api::CountRecentStickersHash(_session));
Api::CountRecentStickersHash(_session, attached), attached);
}
void ApiWrap::requestRecentStickersWithHash(int32 hash) {
if (_recentStickersUpdateRequest) {
void ApiWrap::requestRecentStickersWithHash(int32 hash, bool attached) {
const auto requestId = [=]() -> mtpRequestId & {
return attached
? _recentAttachedStickersUpdateRequest
: _recentStickersUpdateRequest;
};
if (requestId()) {
return;
}
_recentStickersUpdateRequest = request(MTPmessages_GetRecentStickers(
MTP_flags(0),
const auto finish = [=] {
auto &stickers = _session->data().stickers();
if (attached) {
stickers.setLastRecentAttachedUpdate(crl::now());
} else {
stickers.setLastRecentUpdate(crl::now());
}
requestId() = 0;
};
const auto flags = attached
? MTPmessages_getRecentStickers::Flag::f_attached
: MTPmessages_getRecentStickers::Flags(0);
requestId() = request(MTPmessages_GetRecentStickers(
MTP_flags(flags),
MTP_int(hash)
)).done([=](const MTPmessages_RecentStickers &result) {
_session->data().stickers().setLastRecentUpdate(crl::now());
_recentStickersUpdateRequest = 0;
finish();
switch (result.type()) {
case mtpc_messages_recentStickersNotModified: return;
case mtpc_messages_recentStickers: {
auto &d = result.c_messages_recentStickers();
_session->data().stickers().specialSetReceived(
Data::Stickers::CloudRecentSetId,
attached
? Data::Stickers::CloudRecentAttachedSetId
: Data::Stickers::CloudRecentSetId,
tr::lng_recent_stickers(tr::now),
d.vstickers().v,
d.vhash().v,
@@ -3039,8 +3185,7 @@ void ApiWrap::requestRecentStickersWithHash(int32 hash) {
default: Unexpected("Type in ApiWrap::recentStickersDone()");
}
}).fail([=](const MTP::Error &error) {
_session->data().stickers().setLastRecentUpdate(crl::now());
_recentStickersUpdateRequest = 0;
finish();
LOG(("App Fail: Failed to get recent stickers!"));
}).send();
@@ -3947,9 +4092,12 @@ void ApiWrap::sendFile(
void ApiWrap::sendUploadedPhoto(
FullMsgId localId,
const MTPInputFile &file,
Api::SendOptions options) {
Api::SendOptions options,
std::vector<MTPInputDocument> attachedStickers) {
if (const auto item = _session->data().message(localId)) {
const auto media = Api::PrepareUploadedPhoto(file);
const auto media = Api::PrepareUploadedPhoto(
file,
std::move(attachedStickers));
if (const auto groupId = item->groupId()) {
uploadAlbumMedia(item, groupId, media);
} else {
@@ -3962,12 +4110,17 @@ void ApiWrap::sendUploadedDocument(
FullMsgId localId,
const MTPInputFile &file,
const std::optional<MTPInputFile> &thumb,
Api::SendOptions options) {
Api::SendOptions options,
std::vector<MTPInputDocument> attachedStickers) {
if (const auto item = _session->data().message(localId)) {
if (!item->media() || !item->media()->document()) {
return;
}
const auto media = Api::PrepareUploadedDocument(item, file, thumb);
const auto media = Api::PrepareUploadedDocument(
item,
file,
thumb,
std::move(attachedStickers));
const auto groupId = item->groupId();
if (groupId) {
uploadAlbumMedia(item, groupId, media);
@@ -4368,6 +4521,8 @@ void ApiWrap::sendMediaWithRandomId(
caption.entities,
Api::ConvertOption::SkipLocal);
const auto updateRecentStickers = Api::HasAttachedStickers(media);
const auto flags = MTPmessages_SendMedia::Flags(0)
| (replyTo
? MTPmessages_SendMedia::Flag::f_reply_to_msg_id
@@ -4400,6 +4555,10 @@ void ApiWrap::sendMediaWithRandomId(
)).done([=](const MTPUpdates &result) {
applyUpdates(result);
finish();
if (updateRecentStickers) {
requestRecentStickersForce(true);
}
}).fail([=](const MTP::Error &error) {
sendMessageFail(error, peer, randomId, itemId);
finish();

View File

@@ -280,9 +280,11 @@ public:
void requestStickerSets();
void saveStickerSets(
const Data::StickersSetsOrder &localOrder,
const Data::StickersSetsOrder &localRemoved);
const Data::StickersSetsOrder &localRemoved,
bool setsMasks);
void updateStickers();
void requestRecentStickersForce();
void updateMasks();
void requestRecentStickersForce(bool attached = false);
void setGroupStickerSet(
not_null<ChannelData*> megagroup,
const MTPInputStickerSet &set);
@@ -339,12 +341,6 @@ public:
not_null<UserData*> user,
PhotoId afterId);
void stickerSetInstalled(uint64 setId) {
_stickerSetInstalled.fire_copy(setId);
}
auto stickerSetInstalled() const {
return _stickerSetInstalled.events();
}
void readFeaturedSetDelayed(uint64 setId);
void parseChannelParticipants(
@@ -410,12 +406,14 @@ public:
void sendUploadedPhoto(
FullMsgId localId,
const MTPInputFile &file,
Api::SendOptions options);
Api::SendOptions options,
std::vector<MTPInputDocument> attachedStickers);
void sendUploadedDocument(
FullMsgId localId,
const MTPInputFile &file,
const std::optional<MTPInputFile> &thumb,
Api::SendOptions options);
Api::SendOptions options,
std::vector<MTPInputDocument> attachedStickers);
void cancelLocalItem(not_null<HistoryItem*> item);
@@ -542,12 +540,9 @@ private:
mtpRequestId req);
void gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result);
void stickerSetDisenabled(mtpRequestId requestId);
void stickersSaveOrder();
void requestStickers(TimeId now);
void requestRecentStickers(TimeId now);
void requestRecentStickersWithHash(int32 hash);
void requestStickers(TimeId now, bool masks = false);
void requestRecentStickers(TimeId now, bool attached = false);
void requestRecentStickersWithHash(int32 hash, bool attached = false);
void requestFavedStickers(TimeId now);
void requestFeaturedStickers(TimeId now);
void requestSavedGifs(TimeId now);
@@ -685,12 +680,16 @@ private:
base::Timer _draftsSaveTimer;
base::flat_set<mtpRequestId> _stickerSetDisenableRequests;
Data::StickersSetsOrder _stickersOrder;
base::flat_set<mtpRequestId> _maskSetDisenableRequests;
mtpRequestId _masksReorderRequestId = 0;
mtpRequestId _stickersReorderRequestId = 0;
mtpRequestId _stickersClearRecentRequestId = 0;
mtpRequestId _stickersClearRecentAttachedRequestId = 0;
mtpRequestId _stickersUpdateRequest = 0;
mtpRequestId _masksUpdateRequest = 0;
mtpRequestId _recentStickersUpdateRequest = 0;
mtpRequestId _recentAttachedStickersUpdateRequest = 0;
mtpRequestId _favedStickersUpdateRequest = 0;
mtpRequestId _featuredStickersUpdateRequest = 0;
mtpRequestId _savedGifsUpdateRequest = 0;
@@ -731,8 +730,6 @@ private:
base::Observable<PeerData*> _fullPeerUpdated;
rpl::event_stream<uint64> _stickerSetInstalled;
mtpRequestId _topPromotionRequestId = 0;
std::pair<QString, uint32> _topPromotionKey;
TimeId _topPromotionNextRequestTime = TimeId(0);

View File

@@ -282,8 +282,4 @@ namespace App {
return result;
}
QPixmap pixmapFromImageInPlace(QImage &&image) {
return QPixmap::fromImage(std::move(image), Qt::ColorOnly);
}
}

View File

@@ -45,6 +45,5 @@ namespace App {
constexpr auto kImageSizeLimit = 64 * 1024 * 1024; // Open images up to 64mb jpg/png/gif
QImage readImage(QByteArray data, QByteArray *format = nullptr, bool opaque = true, bool *animated = nullptr);
QImage readImage(const QString &file, QByteArray *format = nullptr, bool opaque = true, bool *animated = nullptr, QByteArray *content = 0);
QPixmap pixmapFromImageInPlace(QImage &&image);
};

View File

@@ -13,7 +13,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/openssl_help.h"
#include "boxes/confirm_box.h"
#include "boxes/confirm_phone_box.h" // ExtractPhonePrefix.
#include "boxes/photo_crop_box.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/peers/add_participants_box.h"
#include "boxes/peers/edit_participant_box.h"
@@ -466,6 +465,7 @@ void GroupInfoBox::prepare() {
_photo.create(
this,
&_navigation->parentController()->window(),
((_type == Type::Channel)
? tr::lng_create_channel_crop
: tr::lng_create_group_crop)(tr::now),

View File

@@ -45,6 +45,6 @@ void AutoLockBox::durationChanged(int seconds) {
Core::App().settings().setAutoLock(seconds);
Core::App().saveSettingsDelayed();
Core::App().checkAutoLock();
Core::App().checkAutoLock(crl::now());
closeBox();
}

View File

@@ -21,7 +21,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/background_preview_box.h"
#include "boxes/confirm_box.h"
#include "window/window_session_controller.h"
#include "app.h"
#include "styles/style_overview.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
@@ -337,7 +336,7 @@ void BackgroundBox::Inner::validatePaperThumbnail(
Data::PatternColor(color),
paper.data.patternIntensity());
}
paper.thumbnail = App::pixmapFromImageInPlace(TakeMiddleSample(
paper.thumbnail = Ui::PixmapFromImage(TakeMiddleSample(
original,
st::backgroundSize));
paper.thumbnail.setDevicePixelRatio(cRetinaFactor());

View File

@@ -29,7 +29,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/confirm_box.h"
#include "boxes/background_preview_box.h"
#include "window/window_session_controller.h"
#include "app.h"
#include "styles/style_chat.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
@@ -399,7 +398,7 @@ BackgroundPreviewBox::BackgroundPreviewBox(
QWidget*,
not_null<Window::SessionController*> controller,
const Data::WallPaper &paper)
: SimpleElementDelegate(controller)
: SimpleElementDelegate(controller, [=] { update(); })
, _controller(controller)
, _text1(GenerateTextItem(
delegate(),
@@ -688,8 +687,8 @@ void BackgroundPreviewBox::setScaledFromImage(
if (!_full.isNull()) {
startFadeInFrom(std::move(_scaled));
}
_scaled = App::pixmapFromImageInPlace(std::move(image));
_blurred = App::pixmapFromImageInPlace(std::move(blurred));
_scaled = Ui::PixmapFromImage(std::move(image));
_blurred = Ui::PixmapFromImage(std::move(blurred));
if (_blur && (!_paper.document() || !_full.isNull())) {
_blur->setDisabled(false);
}

View File

@@ -76,10 +76,6 @@ defaultFeedUserpicButton: FeedUserpicButton {
innerPart: defaultUserpicButton;
}
cropPointSize: 10px;
cropSkip: 13px;
cropMinSize: 20px;
confirmInviteTitle: FlatLabel(defaultFlatLabel) {
align: align(center);
minWidth: 320px;
@@ -386,6 +382,7 @@ confirmCaptionArea: InputField(defaultInputField) {
textMargins: margins(1px, 26px, 31px, 4px);
heightMax: 158px;
}
confirmEditCaptionAreaHeightMax: 78px;
confirmBg: windowBgOver;
confirmMaxHeight: 245px;
confirmMaxHeightSkip: 25px;
@@ -427,33 +424,42 @@ backgroundScroll: ScrollArea(boxScroll) {
deltab: 10px;
}
editMediaButtonSize: 28px;
editMediaButtonSkip: 8px;
sendMediaPreviewSize: 308px;
sendMediaPreviewHeightMax: 1280;
sendMediaRowSkip: 10px;
editMediaButtonSize: 32px;
editMediaButtonSkip: 5px;
editMediaButtonFileSkipRight: 1px;
editMediaButtonFileSkipTop: 7px;
editMediaButtonIconFile: icon {{ "settings_edit", menuIconFg }};
editMediaButtonIconPhoto: icon {{ "settings_edit", roundedFg }};
editMediaButton: IconButton {
editMediaButtonIconFile: icon {{ "send_media/send_media_replace", menuIconFg }};
editMediaButton: IconButton(defaultIconButton) {
width: editMediaButtonSize;
height: editMediaButtonSize;
icon: editMediaButtonIconPhoto;
icon: editMediaButtonIconFile;
rippleAreaSize: editMediaButtonSize;
ripple: defaultRippleAnimation;
}
editMediaHintLabel: FlatLabel(defaultFlatLabel) {
textFg: windowSubTextFg;
minWidth: sendMediaPreviewSize;
}
// SendFilesBox
sendBoxAlbumGroupEditInternalSkip: 8px;
sendBoxAlbumGroupSkipRight: 6px;
sendBoxAlbumGroupSkipTop: 6px;
sendBoxAlbumGroupRadius: 13px;
sendBoxAlbumGroupHeight: 26px;
sendBoxAlbumGroupSkipRight: 5px;
sendBoxAlbumGroupSkipTop: 5px;
sendBoxAlbumGroupRadius: 4px;
sendBoxAlbumGroupSize: size(62px, 25px);
sendBoxAlbumSmallGroupSize: size(30px, 25px);
sendBoxFileGroupSkipTop: 2px;
sendBoxFileGroupSkipRight: 8px;
sendBoxFileGroupSkipRight: 5px;
sendBoxFileGroupEditInternalSkip: -1px;
sendBoxAlbumGroupButtonFile: IconButton(editMediaButton) {
@@ -462,16 +468,11 @@ sendBoxAlbumGroupButtonFile: IconButton(editMediaButton) {
}
}
sendBoxAlbumGroupEditButtonIconFile: editMediaButtonIconFile;
sendBoxAlbumGroupDeleteButtonIconFile: icon {{ "history_file_cancel", menuIconFg, point(6px, 6px) }};
sendBoxAlbumGroupDeleteButtonIconFile: icon {{ "send_media/send_media_delete", menuIconFg }};
sendBoxAlbumGroupButtonMediaEdit: icon {{ "settings_edit", roundedFg, point(3px, -2px) }};
sendBoxAlbumGroupButtonMediaDelete: icon {{ "history_file_cancel", roundedFg, point(2px, 5px) }};
sendBoxAlbumGroupButtonMedia: IconButton {
width: sendBoxAlbumGroupHeight;
height: sendBoxAlbumGroupHeight;
icon: sendBoxAlbumGroupButtonMediaEdit;
}
sendBoxAlbumButtonMediaEdit: icon {{ "send_media/send_media_replace", roundedFg }};
sendBoxAlbumGroupButtonMediaEdit: icon {{ "send_media/send_media_replace", roundedFg, point(4px, 1px) }};
sendBoxAlbumGroupButtonMediaDelete: icon {{ "send_media/send_media_delete", roundedFg }};
// End of SendFilesBox
@@ -523,6 +524,7 @@ usernameTextStyle: TextStyle(boxTextStyle, passcodeTextStyle) {
}
usernameDefaultFg: windowSubTextFg;
editMediaLabelMargins: margins(0px, 11px, 0px, 0px);
editMediaCheckboxMargins: margins(0px, 15px, 23px, 15px);
downloadPathSkip: 10px;
@@ -649,10 +651,6 @@ groupStickersField: InputField(defaultMultiSelectSearchField) {
}
groupStickersSubTitleHeight: 36px;
sendMediaPreviewSize: 308px;
sendMediaPreviewHeightMax: 1280;
sendMediaRowSkip: 10px;
proxyUsePadding: margins(22px, 6px, 22px, 5px);
proxyTryIPv6Padding: margins(22px, 8px, 22px, 5px);
proxyRowPadding: margins(22px, 8px, 8px, 8px);

View File

@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_editing.h"
#include "api/api_text_entities.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
#include "chat_helpers/emoji_suggestions_widget.h"
#include "chat_helpers/message_field.h"
#include "chat_helpers/tabbed_panel.h"
@@ -50,20 +51,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/format_song_document_name.h"
#include "ui/text/format_values.h"
#include "ui/text/text_options.h"
#include "ui/chat/attach/attach_controls.h"
#include "ui/chat/attach/attach_prepare.h"
#include "ui/controls/emoji_button.h"
#include "ui/toast/toast.h"
#include "ui/cached_round_corners.h"
#include "ui/abstract_button.h"
#include "ui/ui_utility.h"
#include "window/window_session_controller.h"
#include "confirm_box.h"
#include "apiwrap.h"
#include "app.h" // App::pixmapFromImageInPlace.
#include "facades.h" // App::LambdaDelayed.
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_chat.h"
#include "editor/photo_editor_layer_widget.h"
#include <QtCore/QMimeData>
namespace {
@@ -204,7 +209,7 @@ EditCaptionBox::EditCaptionBox(
| Images::Option::RoundedTopRight
| Images::Option::RoundedBottomLeft
| Images::Option::RoundedBottomRight;
_thumb = App::pixmapFromImageInPlace(Images::prepare(
_thumb = Ui::PixmapFromImage(Images::prepare(
image->original(),
_thumbw * cIntRetinaFactor(),
0,
@@ -334,7 +339,7 @@ EditCaptionBox::EditCaptionBox(
const auto prepareBasicThumb = _refreshThumbnail;
const auto scaleThumbDown = [=] {
_thumb = App::pixmapFromImageInPlace(_thumb.toImage().scaled(
_thumb = Ui::PixmapFromImage(_thumb.toImage().scaled(
_thumbw * cIntRetinaFactor(),
_thumbh * cIntRetinaFactor(),
Qt::KeepAspectRatio,
@@ -381,6 +386,20 @@ EditCaptionBox::EditCaptionBox(
InitSpellchecker(_controller, _field);
auto label = object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
this,
object_ptr<Ui::FlatLabel>(
this,
tr::lng_edit_photo_editor_hint(tr::now),
st::editMediaHintLabel),
st::editMediaLabelMargins);
_hintLabel = label.data();
_hintLabel->toggle(
_controller->session().settings().photoEditorHintShown()
? _photo
: false,
anim::type::instant);
auto r = object_ptr<Ui::SlideWrap<Ui::Checkbox>>(
this,
object_ptr<Ui::Checkbox>(
@@ -402,6 +421,48 @@ EditCaptionBox::EditCaptionBox(
) | rpl::start_with_next([=] {
closeBox();
}, lifetime());
_photoEditorOpens.events(
) | rpl::start_with_next([=, controller = _controller] {
const auto previewWidth = st::sendMediaPreviewSize;
if (!_preparedList.files.empty()) {
Editor::OpenWithPreparedFile(
this,
controller,
&_preparedList.files.front(),
previewWidth,
[=] { updateEditPreview(); });
} else {
auto callback = [=](const Editor::PhotoModifications &mods) {
if (!mods) {
return;
}
auto copy = computeImage()->original();
_preparedList = Storage::PrepareMediaFromImage(
std::move(copy),
QByteArray(),
previewWidth);
using ImageInfo = Ui::PreparedFileInformation::Image;
auto &file = _preparedList.files.front();
const auto image = std::get_if<ImageInfo>(
&file.information->media);
image->modifications = mods;
Storage::UpdateImageDetails(file, previewWidth);
updateEditPreview();
};
const auto fileImage = std::make_shared<Image>(*computeImage());
controller->showLayer(
std::make_unique<Editor::LayerWidget>(
this,
&controller->window(),
fileImage,
Editor::PhotoModifications(),
std::move(callback)),
Ui::LayerOption::KeepOther);
}
}, lifetime());
}
EditCaptionBox::~EditCaptionBox() = default;
@@ -594,9 +655,13 @@ void EditCaptionBox::updateEditPreview() {
const auto showCheckbox = _photo && (_albumType == Ui::AlbumType::None);
_wayWrap->toggle(showCheckbox, anim::type::instant);
if (_controller->session().settings().photoEditorHintShown()) {
_hintLabel->toggle(_photo, anim::type::instant);
}
_photoEditorButton->setVisible(_photo);
if (!_doc) {
_thumb = App::pixmapFromImageInPlace(
_thumb = Ui::PixmapFromImage(
file->preview.scaled(
st::sendMediaPreviewSize * cIntRetinaFactor(),
(st::confirmMaxHeight - (showCheckbox
@@ -614,17 +679,13 @@ void EditCaptionBox::updateEditPreview() {
}
}
updateEditMediaButton();
updateCaptionMaxHeight();
captionResized();
}
void EditCaptionBox::updateEditMediaButton() {
const auto icon = _doc
? &st::editMediaButtonIconFile
: &st::editMediaButtonIconPhoto;
const auto color = _doc ? &st::windowBgRipple : &st::roundedBg;
_editMedia->setIconOverride(icon);
_editMedia->setRippleColorOverride(color);
_editMedia->setForceRippled(!_doc, anim::type::instant);
_editMedia->setVisible(!_doc);
_editFile->setVisible(_doc);
}
void EditCaptionBox::createEditMediaButton() {
@@ -677,13 +738,26 @@ void EditCaptionBox::createEditMediaButton() {
lifetime());
// Create edit media button.
_editMedia.create(this, st::editMediaButton);
_editMedia.create(this, Ui::AttachControls::Type::EditOnly);
_editFile.create(this, st::editMediaButton);
updateEditMediaButton();
_editMedia->setClickedCallback(
_editFile->setClickedCallback(
App::LambdaDelayed(
st::historyAttach.ripple.hideDuration,
this,
buttonCallback));
_editMedia->editRequests(
) | rpl::start_with_next(buttonCallback, _editMedia->lifetime());
_photoEditorButton = base::make_unique_q<Ui::AbstractButton>(this);
_photoEditorButton->clicks(
) | rpl::to_empty | rpl::start_to_stream(
_photoEditorOpens,
_photoEditorButton->lifetime());
_photoEditorButton->raise();
_editMedia->raise();
}
void EditCaptionBox::prepare() {
@@ -728,6 +802,7 @@ void EditCaptionBox::prepare() {
auto cursor = _field->textCursor();
cursor.movePosition(QTextCursor::End);
_field->setTextCursor(cursor);
updateCaptionMaxHeight();
setupDragArea();
}
@@ -771,6 +846,28 @@ void EditCaptionBox::captionResized() {
update();
}
void EditCaptionBox::updateCaptionMaxHeight() {
// Save.
const auto wasCursor = _field->textCursor();
const auto position = wasCursor.position();
const auto anchor = wasCursor.anchor();
const auto text = _field->getTextWithAppliedMarkdown();
_field->setTextWithTags({});
_field->setMaxHeight(_doc
? st::confirmCaptionArea.heightMax
: (st::confirmEditCaptionAreaHeightMax - (_hintLabel->height() / 2)));
// Restore.
_field->setTextWithTags(text);
auto cursor = _field->textCursor();
cursor.setPosition(anchor);
if (position != anchor) {
cursor.setPosition(position, QTextCursor::KeepAnchor);
}
_field->setTextCursor(cursor);
}
void EditCaptionBox::setupEmojiPanel() {
const auto container = getDelegate()->outerContainer();
_emojiPanel = base::make_unique_q<ChatHelpers::TabbedPanel>(
@@ -843,6 +940,9 @@ void EditCaptionBox::updateBoxSize() {
if (_photo) {
newHeight += _wayWrap->height() / 2;
}
if (_hintLabel->toggled()) {
newHeight += _hintLabel->height();
}
const auto &st = isThumbedLayout()
? st::msgFileThumbLayout
: st::msgFileLayout;
@@ -887,39 +987,56 @@ void EditCaptionBox::startStreamedPlayer() {
void EditCaptionBox::paintEvent(QPaintEvent *e) {
BoxContent::paintEvent(e);
const auto &padding = st::boxPhotoPadding;
Painter p(this);
if (_photo || _animated) {
const auto th = std::max(_gifh, _thumbh);
if (_thumbx > st::boxPhotoPadding.left()) {
p.fillRect(st::boxPhotoPadding.left(), st::boxPhotoPadding.top(), _thumbx - st::boxPhotoPadding.left(), th, st::confirmBg);
if (_thumbx > padding.left()) {
p.fillRect(
padding.left(),
padding.top(),
_thumbx - padding.left(),
th,
st::confirmBg);
}
if (_thumbx + _thumbw < width() - st::boxPhotoPadding.right()) {
p.fillRect(_thumbx + _thumbw, st::boxPhotoPadding.top(), width() - st::boxPhotoPadding.right() - _thumbx - _thumbw, th, st::confirmBg);
if (_thumbx + _thumbw < width() - padding.right()) {
p.fillRect(
_thumbx + _thumbw,
padding.top(),
width() - padding.right() - _thumbx - _thumbw,
th,
st::confirmBg);
}
checkStreamedIsStarted();
if (_streamed
&& _streamed->player().ready()
&& !_streamed->player().videoSize().isEmpty()) {
const auto s = QSize(_gifw, _gifh);
const auto paused = _controller->isGifPausedAtLeastFor(Window::GifPauseReason::Layer);
const auto paused = _controller->isGifPausedAtLeastFor(
Window::GifPauseReason::Layer);
auto request = ::Media::Streaming::FrameRequest();
request.outer = s * cIntRetinaFactor();
request.resize = s * cIntRetinaFactor();
p.drawImage(
QRect(_gifx, st::boxPhotoPadding.top(), _gifw, _gifh),
QRect(_gifx, padding.top(), _gifw, _gifh),
_streamed->frame(request));
if (!paused) {
_streamed->markFrameShown();
}
} else {
const auto offset = _gifh ? ((_gifh - _thumbh) / 2) : 0;
p.drawPixmap(_thumbx, st::boxPhotoPadding.top() + offset, _thumb);
p.drawPixmap(_thumbx, padding.top() + offset, _thumb);
}
if (_animated && !_streamed) {
const auto &st = st::msgFileLayout;
QRect inner(_thumbx + (_thumbw - st.thumbSize) / 2, st::boxPhotoPadding.top() + (th - st.thumbSize) / 2, st.thumbSize, st.thumbSize);
QRect inner(
_thumbx + (_thumbw - st.thumbSize) / 2,
padding.top() + (th - st.thumbSize) / 2,
st.thumbSize,
st.thumbSize);
p.setPen(Qt::NoPen);
p.setBrush(st::msgDateImgBg);
@@ -935,21 +1052,26 @@ void EditCaptionBox::paintEvent(QPaintEvent *e) {
const auto &st = isThumbedLayout()
? st::msgFileThumbLayout
: st::msgFileLayout;
const auto w = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right();
const auto w = width() - padding.left() - padding.right();
const auto h = 0 + st.thumbSize + 0;
const auto nameleft = 0 + st.thumbSize + st.padding.right();
const auto nametop = st.nameTop - st.padding.top();
const auto nameright = 0;
const auto statustop = st.statusTop - st.padding.top();
const auto editButton = _isAllowedEditMedia
? _editMedia->width() + st::editMediaButtonSkip
? _editFile->width() + st::editMediaButtonSkip
: 0;
const auto namewidth = w - nameleft - editButton;
const auto x = (width() - w) / 2, y = st::boxPhotoPadding.top();
const auto x = (width() - w) / 2, y = padding.top();
// Ui::FillRoundCorner(p, x, y, w, h, st::msgInBg, Ui::MessageInCorners, &st::msgInShadow);
const auto rthumb = style::rtlrect(x + 0, y + 0, st.thumbSize, st.thumbSize, width());
const auto rthumb = style::rtlrect(
x + 0,
y + 0,
st.thumbSize,
st.thumbSize,
width());
if (isThumbedLayout()) {
p.drawPixmap(rthumb.topLeft(), _thumb);
} else {
@@ -972,7 +1094,12 @@ void EditCaptionBox::paintEvent(QPaintEvent *e) {
}
p.setFont(st::semiboldFont);
p.setPen(st::historyFileNameInFg);
_name.drawLeftElided(p, x + nameleft, y + nametop, namewidth, width());
_name.drawLeftElided(
p,
x + nameleft,
y + nametop,
namewidth,
width());
const auto &status = st::mediaInFg;
p.setFont(st::normalFont);
@@ -981,38 +1108,67 @@ void EditCaptionBox::paintEvent(QPaintEvent *e) {
} else {
p.setFont(st::boxTitleFont);
p.setPen(st::boxTextFg);
p.drawTextLeft(_field->x(), st::boxPhotoPadding.top(), width(), tr::lng_edit_message(tr::now));
p.drawTextLeft(
_field->x(),
padding.top(),
width(),
tr::lng_edit_message(tr::now));
}
if (!_error.isEmpty()) {
p.setFont(st::normalFont);
p.setPen(st::boxTextFgError);
p.drawTextLeft(_field->x(), _field->y() + _field->height() + errorTopSkip(), width(), _error);
p.drawTextLeft(
_field->x(),
_field->y() + _field->height() + errorTopSkip(),
width(),
_error);
}
if (_isAllowedEditMedia) {
_editMedia->moveToRight(
st::boxPhotoPadding.right() + (_doc
? st::editMediaButtonFileSkipRight
: st::editMediaButtonSkip),
st::boxPhotoPadding.top() + (_doc
? st::editMediaButtonFileSkipTop
: st::editMediaButtonSkip));
if (_doc) {
_editFile->moveToRight(
padding.right() + st::editMediaButtonFileSkipRight,
padding.top() + st::editMediaButtonFileSkipTop);
} else {
_editMedia->moveToRight(
padding.right() + st::editMediaButtonSkip,
padding.top() + st::editMediaButtonSkip);
}
}
}
void EditCaptionBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
const auto previewBottom = st::boxPhotoPadding.top() + _thumbh;
const auto hintToggled = _hintLabel->toggled();
if (hintToggled) {
_hintLabel->resize(st::sendMediaPreviewSize, _hintLabel->height());
_hintLabel->moveToLeft(st::boxPhotoPadding.left(), previewBottom);
}
if (_photo) {
_wayWrap->resize(st::sendMediaPreviewSize, _wayWrap->height());
_wayWrap->moveToLeft(
st::boxPhotoPadding.left(),
st::boxPhotoPadding.top() + _thumbh);
hintToggled
? _hintLabel->y() + _hintLabel->height()
: previewBottom);
_photoEditorButton->resize(_thumbw, _thumbh);
_photoEditorButton->moveToLeft(_thumbx, st::boxPhotoPadding.top());
}
_field->resize(st::sendMediaPreviewSize, _field->height());
_field->moveToLeft(st::boxPhotoPadding.left(), height() - st::normalFont->height - errorTopSkip() - _field->height());
_field->moveToLeft(
st::boxPhotoPadding.left(),
height()
- st::normalFont->height
- errorTopSkip()
- _field->height());
_emojiToggle->moveToLeft(
(st::boxPhotoPadding.left()
+ st::sendMediaPreviewSize
@@ -1048,6 +1204,11 @@ void EditCaptionBox::save() {
action.options = options;
action.replaceMediaOf = item->fullId().msg;
if (Storage::ApplyModifications(_preparedList)) {
_controller->session().settings().incrementPhotoEditorHintShown();
_controller->session().saveSettings();
}
_controller->session().api().editMedia(
std::move(_preparedList),
(!_asFile && _photo) ? SendMediaType::Photo : SendMediaType::File,
@@ -1099,8 +1260,10 @@ void EditCaptionBox::setName(QString nameString, qint64 size) {
}
void EditCaptionBox::keyPressEvent(QKeyEvent *e) {
if ((e->key() == Qt::Key_E || e->key() == Qt::Key_O)
&& e->modifiers() == Qt::ControlModifier) {
const auto ctrl = e->modifiers().testFlag(Qt::ControlModifier);
if ((e->key() == Qt::Key_E) && ctrl) {
_photoEditorOpens.fire({});
} else if ((e->key() == Qt::Key_O) && ctrl) {
_editMediaClicks.fire({});
} else {
e->ignore();

View File

@@ -29,11 +29,13 @@ class DocumentMedia;
} // namespace Data
namespace Ui {
class AbstractButton;
class InputField;
class EmojiButton;
class IconButton;
class Checkbox;
enum class AlbumType;
class AttachControlsWidget;
} // namespace Ui
namespace Window {
@@ -90,6 +92,7 @@ private:
bool fileFromClipboard(not_null<const QMimeData*> data);
void updateEditPreview();
void updateEditMediaButton();
void updateCaptionMaxHeight();
int errorTopSkip() const;
@@ -122,6 +125,7 @@ private:
object_ptr<Ui::EmojiButton> _emojiToggle = { nullptr };
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
base::unique_qptr<QObject> _emojiFilter;
base::unique_qptr<Ui::AbstractButton> _photoEditorButton;
int _thumbx = 0;
int _thumbw = 0;
@@ -139,13 +143,16 @@ private:
mtpRequestId _saveRequestId = 0;
object_ptr<Ui::IconButton> _editMedia = nullptr;
object_ptr<Ui::AttachControlsWidget> _editMedia = nullptr;
object_ptr<Ui::IconButton> _editFile = nullptr;
Ui::SlideWrap<Ui::RpWidget> *_wayWrap = nullptr;
Ui::SlideWrap<Ui::RpWidget> *_hintLabel = nullptr;
QString _newMediaPath;
Ui::AlbumType _albumType = Ui::AlbumType();
bool _isAllowedEditMedia = false;
bool _asFile = false;
rpl::event_stream<> _editMediaClicks;
rpl::event_stream<> _photoEditorOpens;
QString _error;

View File

@@ -12,7 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/input_fields.h"
#include "ui/ui_utility.h"
#include "base/platform/base_platform_info.h"
#include "app.h"
#include "styles/style_boxes.h"
#include "styles/style_media_view.h"
@@ -423,7 +422,7 @@ void EditColorBox::Slider::generatePixmap() {
if (!isHorizontal()) {
image = std::move(image).transformed(QTransform(0, -1, 1, 0, 0, 0));
}
_pixmap = App::pixmapFromImageInPlace(std::move(image));
_pixmap = Ui::PixmapFromImage(std::move(image));
} else if (_type == Type::Opacity) {
auto color = anim::shifted(QColor(255, 255, 255, 255));
auto transparent = anim::shifted(QColor(255, 255, 255, 0));
@@ -459,7 +458,7 @@ void EditColorBox::Slider::generatePixmap() {
if (!isHorizontal()) {
image = std::move(image).transformed(QTransform(0, -1, 1, 0, 0, 0));
}
_pixmap = App::pixmapFromImageInPlace(std::move(image));
_pixmap = Ui::PixmapFromImage(std::move(image));
}
}
@@ -530,7 +529,7 @@ void EditColorBox::Slider::setLightnessLimits(int min, int max) {
}
void EditColorBox::Slider::updatePixmapFromMask() {
_pixmap = App::pixmapFromImageInPlace(style::colorizeImage(_mask, _color));
_pixmap = Ui::PixmapFromImage(style::colorizeImage(_mask, _color));
}
void EditColorBox::Slider::updateCurrentPoint(QPoint localPosition) {

View File

@@ -495,6 +495,7 @@ object_ptr<Ui::RpWidget> Controller::createPhotoEdit() {
_wrap,
object_ptr<Ui::UserpicButton>(
_wrap,
&_navigation->parentController()->window(),
_peer,
Ui::UserpicButton::Role::ChangePhoto,
st::defaultUserpicButton),

View File

@@ -1,270 +0,0 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/photo_crop_box.h"
#include "lang/lang_keys.h"
#include "ui/widgets/buttons.h"
#include "ui/ui_utility.h"
#include "app.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
PhotoCropBox::PhotoCropBox(
QWidget*,
const QImage &img,
const QString &title)
: _title(title)
, _img(img) {
}
void PhotoCropBox::prepare() {
addButton(tr::lng_settings_save(), [this] { sendPhoto(); });
addButton(tr::lng_cancel(), [this] { closeBox(); });
int32 s = st::boxWideWidth - st::boxPhotoPadding.left() - st::boxPhotoPadding.right();
_thumb = App::pixmapFromImageInPlace(_img.scaled(s * cIntRetinaFactor(), s * cIntRetinaFactor(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
_thumb.setDevicePixelRatio(cRetinaFactor());
_mask = QImage(_thumb.size(), QImage::Format_ARGB32_Premultiplied);
_mask.setDevicePixelRatio(cRetinaFactor());
_fade = QImage(_thumb.size(), QImage::Format_ARGB32_Premultiplied);
_fade.setDevicePixelRatio(cRetinaFactor());
_thumbw = _thumb.width() / cIntRetinaFactor();
_thumbh = _thumb.height() / cIntRetinaFactor();
if (_thumbw > _thumbh) {
_cropw = _thumbh - 20;
} else {
_cropw = _thumbw - 20;
}
_cropx = (_thumbw - _cropw) / 2;
_cropy = (_thumbh - _cropw) / 2;
_thumbx = (st::boxWideWidth - _thumbw) / 2;
_thumby = st::boxPhotoPadding.top();
setMouseTracking(true);
setDimensions(st::boxWideWidth, st::boxPhotoPadding.top() + _thumbh + st::boxPhotoPadding.bottom() + st::boxTextFont->height + st::cropSkip);
}
void PhotoCropBox::mousePressEvent(QMouseEvent *e) {
if (e->button() == Qt::LeftButton) {
_downState = mouseState(e->pos());
_fromposx = e->pos().x();
_fromposy = e->pos().y();
_fromcropx = _cropx;
_fromcropy = _cropy;
_fromcropw = _cropw;
}
return BoxContent::mousePressEvent(e);
}
int PhotoCropBox::mouseState(QPoint p) {
p -= QPoint(_thumbx, _thumby);
int32 delta = st::cropPointSize, mdelta(-delta / 2);
if (QRect(_cropx + mdelta, _cropy + mdelta, delta, delta).contains(p)) {
return 1;
} else if (QRect(_cropx + _cropw + mdelta, _cropy + mdelta, delta, delta).contains(p)) {
return 2;
} else if (QRect(_cropx + _cropw + mdelta, _cropy + _cropw + mdelta, delta, delta).contains(p)) {
return 3;
} else if (QRect(_cropx + mdelta, _cropy + _cropw + mdelta, delta, delta).contains(p)) {
return 4;
} else if (QRect(_cropx, _cropy, _cropw, _cropw).contains(p)) {
return 5;
}
return 0;
}
rpl::producer<QImage> PhotoCropBox::ready() const {
return _readyImages.events();
}
void PhotoCropBox::mouseReleaseEvent(QMouseEvent *e) {
if (_downState) {
_downState = 0;
mouseMoveEvent(e);
}
}
void PhotoCropBox::mouseMoveEvent(QMouseEvent *e) {
if (_downState && !(e->buttons() & Qt::LeftButton)) {
mouseReleaseEvent(e);
}
if (_downState) {
if (_downState == 1) {
int32 dx = e->pos().x() - _fromposx, dy = e->pos().y() - _fromposy, d = (dx < dy) ? dx : dy;
if (_fromcropx + d < 0) {
d = -_fromcropx;
}
if (_fromcropy + d < 0) {
d = -_fromcropy;
}
if (_fromcropw - d < st::cropMinSize) {
d = _fromcropw - st::cropMinSize;
}
if (_cropx != _fromcropx + d || _cropy != _fromcropy + d || _cropw != _fromcropw - d) {
_cropx = _fromcropx + d;
_cropy = _fromcropy + d;
_cropw = _fromcropw - d;
update();
}
} else if (_downState == 2) {
int32 dx = _fromposx - e->pos().x(), dy = e->pos().y() - _fromposy, d = (dx < dy) ? dx : dy;
if (_fromcropx + _fromcropw - d > _thumbw) {
d = _fromcropx + _fromcropw - _thumbw;
}
if (_fromcropy + d < 0) {
d = -_fromcropy;
}
if (_fromcropw - d < st::cropMinSize) {
d = _fromcropw - st::cropMinSize;
}
if (_cropy != _fromcropy + d || _cropw != _fromcropw - d) {
_cropy = _fromcropy + d;
_cropw = _fromcropw - d;
update();
}
} else if (_downState == 3) {
int32 dx = _fromposx - e->pos().x(), dy = _fromposy - e->pos().y(), d = (dx < dy) ? dx : dy;
if (_fromcropx + _fromcropw - d > _thumbw) {
d = _fromcropx + _fromcropw - _thumbw;
}
if (_fromcropy + _fromcropw - d > _thumbh) {
d = _fromcropy + _fromcropw - _thumbh;
}
if (_fromcropw - d < st::cropMinSize) {
d = _fromcropw - st::cropMinSize;
}
if (_cropw != _fromcropw - d) {
_cropw = _fromcropw - d;
update();
}
} else if (_downState == 4) {
int32 dx = e->pos().x() - _fromposx, dy = _fromposy - e->pos().y(), d = (dx < dy) ? dx : dy;
if (_fromcropx + d < 0) {
d = -_fromcropx;
}
if (_fromcropy + _fromcropw - d > _thumbh) {
d = _fromcropy + _fromcropw - _thumbh;
}
if (_fromcropw - d < st::cropMinSize) {
d = _fromcropw - st::cropMinSize;
}
if (_cropx != _fromcropx + d || _cropw != _fromcropw - d) {
_cropx = _fromcropx + d;
_cropw = _fromcropw - d;
update();
}
} else if (_downState == 5) {
int32 dx = e->pos().x() - _fromposx, dy = e->pos().y() - _fromposy;
if (_fromcropx + dx < 0) {
dx = -_fromcropx;
} else if (_fromcropx + _fromcropw + dx > _thumbw) {
dx = _thumbw - _fromcropx - _fromcropw;
}
if (_fromcropy + dy < 0) {
dy = -_fromcropy;
} else if (_fromcropy + _fromcropw + dy > _thumbh) {
dy = _thumbh - _fromcropy - _fromcropw;
}
if (_cropx != _fromcropx + dx || _cropy != _fromcropy + dy) {
_cropx = _fromcropx + dx;
_cropy = _fromcropy + dy;
update();
}
}
}
int32 cursorState = _downState ? _downState : mouseState(e->pos());
QCursor cur(style::cur_default);
if (cursorState == 1 || cursorState == 3) {
cur = style::cur_sizefdiag;
} else if (cursorState == 2 || cursorState == 4) {
cur = style::cur_sizebdiag;
} else if (cursorState == 5) {
cur = style::cur_sizeall;
}
setCursor(cur);
}
void PhotoCropBox::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
sendPhoto();
} else {
BoxContent::keyPressEvent(e);
}
}
void PhotoCropBox::paintEvent(QPaintEvent *e) {
BoxContent::paintEvent(e);
Painter p(this);
p.setFont(st::boxTextFont);
p.setPen(st::boxPhotoTextFg);
p.drawText(QRect(st::boxPhotoPadding.left(), st::boxPhotoPadding.top() + _thumbh + st::boxPhotoPadding.bottom(), width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(), st::boxTextFont->height), _title, style::al_top);
p.translate(_thumbx, _thumby);
p.drawPixmap(0, 0, _thumb);
_mask.fill(Qt::white);
{
Painter p(&_mask);
PainterHighQualityEnabler hq(p);
p.setPen(Qt::NoPen);
p.setBrush(Qt::black);
p.drawEllipse(_cropx, _cropy, _cropw, _cropw);
}
style::colorizeImage(_mask, st::photoCropFadeBg->c, &_fade);
p.drawImage(0, 0, _fade);
int delta = st::cropPointSize;
int mdelta = -delta / 2;
p.fillRect(QRect(_cropx + mdelta, _cropy + mdelta, delta, delta), st::photoCropPointFg);
p.fillRect(QRect(_cropx + _cropw + mdelta, _cropy + mdelta, delta, delta), st::photoCropPointFg);
p.fillRect(QRect(_cropx + _cropw + mdelta, _cropy + _cropw + mdelta, delta, delta), st::photoCropPointFg);
p.fillRect(QRect(_cropx + mdelta, _cropy + _cropw + mdelta, delta, delta), st::photoCropPointFg);
}
void PhotoCropBox::sendPhoto() {
auto from = _img;
if (_img.width() < _thumb.width()) {
from = _thumb.toImage();
}
float64 x = float64(_cropx) / _thumbw, y = float64(_cropy) / _thumbh, w = float64(_cropw) / _thumbw;
int32 ix = int32(x * from.width()), iy = int32(y * from.height()), iw = int32(w * from.width());
if (ix < 0) {
ix = 0;
}
if (ix + iw > from.width()) {
iw = from.width() - ix;
}
if (iy < 0) {
iy = 0;
}
if (iy + iw > from.height()) {
iw = from.height() - iy;
}
int32 offset = ix * from.depth() / 8 + iy * from.bytesPerLine();
QImage cropped(from.constBits() + offset, iw, iw, from.bytesPerLine(), from.format()), tosend;
if (from.format() == QImage::Format_Indexed8) {
cropped.setColorCount(from.colorCount());
cropped.setColorTable(from.colorTable());
}
if (cropped.width() > 1280) {
tosend = cropped.scaled(1280, 1280, Qt::KeepAspectRatio, Qt::SmoothTransformation);
} else if (cropped.width() < 320) {
tosend = cropped.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation);
} else {
tosend = cropped.copy();
}
auto weak = Ui::MakeWeak(this);
_readyImages.fire(std::move(tosend));
if (weak) {
closeBox();
}
}

View File

@@ -1,42 +0,0 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "boxes/abstract_box.h"
class PhotoCropBox : public Ui::BoxContent {
public:
PhotoCropBox(QWidget*, const QImage &img, const QString &title);
int32 mouseState(QPoint p);
rpl::producer<QImage> ready() const;
protected:
void prepare() override;
void keyPressEvent(QKeyEvent *e) override;
void paintEvent(QPaintEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
private:
void sendPhoto();
QString _title;
int32 _downState = 0;
int32 _thumbx, _thumby, _thumbw, _thumbh;
int32 _cropx, _cropy, _cropw;
int32 _fromposx, _fromposy, _fromcropx, _fromcropy, _fromcropw;
QImage _img;
QPixmap _thumb;
QImage _mask, _fade;
rpl::event_stream<QImage> _readyImages;
};

View File

@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/storage_media_prepare.h"
#include "mainwidget.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
#include "mtproto/mtproto_config.h"
#include "chat_helpers/message_field.h"
#include "chat_helpers/send_context_menu.h"
@@ -20,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/tabbed_panel.h"
#include "chat_helpers/tabbed_selector.h"
#include "confirm_box.h"
#include "editor/photo_editor_layer_widget.h"
#include "history/history_drag_area.h"
#include "history/view/history_view_schedule_box.h"
#include "core/file_utilities.h"
@@ -187,6 +189,22 @@ rpl::producer<int> SendFilesBox::Block::itemReplaceRequest() const {
}
}
rpl::producer<int> SendFilesBox::Block::itemModifyRequest() const {
using namespace rpl::mappers;
const auto preview = _preview.get();
const auto from = _from;
if (_isAlbum) {
const auto album = static_cast<Ui::AlbumPreview*>(preview);
return album->thumbModified() | rpl::map(_1 + from);
} else if (_isSingleMedia) {
const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
return media->modifyRequests() | rpl::map_to(from);
} else {
return rpl::never<int>();
}
}
void SendFilesBox::Block::setSendWay(Ui::SendFilesWay way) {
if (!_isAlbum) {
return;
@@ -601,6 +619,16 @@ void SendFilesBox::pushBlock(int from, int till) {
FileDialog::AllOrImagesFilter(),
crl::guard(this, callback));
}, widget->lifetime());
block.itemModifyRequest(
) | rpl::start_with_next([=, controller = _controller](int index) {
Editor::OpenWithPreparedFile(
this,
controller,
&_list.files[index],
st::sendMediaPreviewSize,
[=] { refreshAllAfterChanges(from); });
}, widget->lifetime());
}
void SendFilesBox::refreshControls() {
@@ -640,6 +668,11 @@ void SendFilesBox::setupSendWayControls() {
sendWay.setSendImagesAsPhotos(_sendImagesAsPhotos->checked());
_sendWay = sendWay;
}, lifetime());
_hintLabel.create(
this,
tr::lng_edit_photo_editor_hint(tr::now),
st::editMediaHintLabel);
}
void SendFilesBox::updateSendWayControlsVisibility() {
@@ -647,6 +680,11 @@ void SendFilesBox::updateSendWayControlsVisibility() {
_groupFiles->setVisible(_list.hasGroupOption(onlyOne));
_sendImagesAsPhotos->setVisible(
_list.hasSendImagesAsPhotosOption(onlyOne));
_hintLabel->setVisible(
_controller->session().settings().photoEditorHintShown()
? _list.hasSendImagesAsPhotosOption(false)
: false);
}
void SendFilesBox::setupCaption() {
@@ -850,14 +888,15 @@ void SendFilesBox::updateBoxSize() {
if (_caption) {
footerHeight += st::boxPhotoCaptionSkip + _caption->height();
}
const auto pointers = {
_groupFiles.data(),
_sendImagesAsPhotos.data(),
};
for (auto pointer : pointers) {
const auto pairs = std::array<std::pair<RpWidget*, int>, 3>{ {
{ _groupFiles.data(), st::boxPhotoCompressedSkip },
{ _sendImagesAsPhotos.data(), st::boxPhotoCompressedSkip },
{ _hintLabel.data(), st::editMediaLabelMargins.top() },
} };
for (const auto &pair : pairs) {
const auto pointer = pair.first;
if (pointer && !pointer->isHidden()) {
footerHeight += st::boxPhotoCompressedSkip
+ pointer->heightNoMargins();
footerHeight += pair.second + pointer->heightNoMargins();
}
}
_footerHeight = footerHeight;
@@ -916,16 +955,18 @@ void SendFilesBox::updateControlsGeometry() {
_emojiToggle->update();
}
}
const auto pointers = {
_groupFiles.data(),
_sendImagesAsPhotos.data(),
};
for (const auto pointer : ranges::views::reverse(pointers)) {
const auto pairs = std::array<std::pair<RpWidget*, int>, 3>{ {
{ _hintLabel.data(), st::editMediaLabelMargins.top() },
{ _groupFiles.data(), st::boxPhotoCompressedSkip },
{ _sendImagesAsPhotos.data(), st::boxPhotoCompressedSkip },
} };
for (const auto &pair : ranges::views::reverse(pairs)) {
const auto pointer = pair.first;
if (pointer && !pointer->isHidden()) {
pointer->moveToLeft(
st::boxPhotoPadding.left(),
bottom - pointer->heightNoMargins());
bottom -= st::boxPhotoCompressedSkip + pointer->heightNoMargins();
bottom -= pair.second + pointer->heightNoMargins();
}
}
_scroll->resize(width(), bottom - _titleHeight.current());
@@ -976,6 +1017,12 @@ void SendFilesBox::send(
for (auto &block : _blocks) {
block.applyAlbumOrder();
}
if (Storage::ApplyModifications(_list)) {
_controller->session().settings().incrementPhotoEditorHintShown();
_controller->session().saveSettings();
}
_confirmed = true;
if (_confirmedCallback) {
auto caption = (_caption && !_caption->isHidden())

View File

@@ -35,6 +35,7 @@ struct GroupMediaLayout;
class EmojiButton;
class AlbumPreview;
class VerticalLayout;
class FlatLabel;
} // namespace Ui
namespace Window {
@@ -102,6 +103,7 @@ private:
[[nodiscard]] rpl::producer<int> itemDeleteRequest() const;
[[nodiscard]] rpl::producer<int> itemReplaceRequest() const;
[[nodiscard]] rpl::producer<int> itemModifyRequest() const;
void setSendWay(Ui::SendFilesWay way);
void applyAlbumOrder();
@@ -183,6 +185,7 @@ private:
object_ptr<Ui::Checkbox> _groupFiles = { nullptr };
object_ptr<Ui::Checkbox> _sendImagesAsPhotos = { nullptr };
object_ptr<Ui::FlatLabel> _hintLabel = { nullptr };
rpl::variable<Ui::SendFilesWay> _sendWay = Ui::SendFilesWay();
rpl::variable<int> _footerHeight = 0;

View File

@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/image/image.h"
#include "ui/image/image_location_factory.h"
#include "ui/text/text_utilities.h"
#include "ui/effects/path_shift_gradient.h"
#include "ui/emoji_config.h"
#include "ui/toast/toast.h"
#include "ui/widgets/popup_menu.h"
@@ -68,10 +69,17 @@ public:
void install();
[[nodiscard]] rpl::producer<uint64> setInstalled() const;
[[nodiscard]] rpl::producer<uint64> setArchived() const;
[[nodiscard]] rpl::producer<> updateControls() const;
[[nodiscard]] rpl::producer<Error> errors() const;
void archiveStickers();
bool isMasksSet() const {
return (_setFlags & MTPDstickerSet::Flag::f_masks);
}
~Inner();
protected:
@@ -104,10 +112,6 @@ private:
void gotSet(const MTPmessages_StickerSet &set);
void installDone(const MTPmessages_StickerSetInstallResult &result);
bool isMasksSet() const {
return (_setFlags & MTPDstickerSet::Flag::f_masks);
}
not_null<Lottie::MultiPlayer*> getLottiePlayer();
void showPreview();
@@ -128,6 +132,8 @@ private:
TimeId _setInstallDate = TimeId(0);
ImageWithLocation _setThumbnail;
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
MTPInputStickerSet _input;
mtpRequestId _installRequest = 0;
@@ -138,6 +144,7 @@ private:
int _previewShown = -1;
rpl::event_stream<uint64> _setInstalled;
rpl::event_stream<uint64> _setArchived;
rpl::event_stream<> _updateControls;
rpl::event_stream<Error> _errors;
@@ -186,7 +193,12 @@ void StickerSetBox::prepare() {
_inner->setInstalled(
) | rpl::start_with_next([=](uint64 setId) {
_controller->session().api().stickerSetInstalled(setId);
if (_inner->isMasksSet()) {
Ui::Toast::Show(tr::lng_masks_installed(tr::now));
} else {
auto &stickers = _controller->session().data().stickers();
stickers.notifyStickerSetInstalled(setId);
}
closeBox();
}, lifetime());
@@ -194,6 +206,36 @@ void StickerSetBox::prepare() {
) | rpl::start_with_next([=](Error error) {
handleError(error);
}, lifetime());
_inner->setArchived(
) | rpl::start_with_next([=](uint64 setId) {
const auto isMasks = _inner->isMasksSet();
Ui::Toast::Show(isMasks
? tr::lng_masks_has_been_archived(tr::now)
: tr::lng_stickers_has_been_archived(tr::now));
auto &order = isMasks
? _controller->session().data().stickers().maskSetsOrderRef()
: _controller->session().data().stickers().setsOrderRef();
const auto index = order.indexOf(setId);
if (index != -1) {
order.removeAt(index);
auto &local = _controller->session().local();
if (isMasks) {
local.writeInstalledMasks();
local.writeArchivedMasks();
} else {
local.writeInstalledStickers();
local.writeArchivedStickers();
}
}
_controller->session().data().stickers().notifyUpdated();
closeBox();
}, lifetime());
}
void StickerSetBox::addStickers() {
@@ -220,38 +262,6 @@ void StickerSetBox::handleError(Error error) {
}
}
void StickerSetBox::archiveStickers() {
const auto weak = base::make_weak(_controller.get());
const auto setId = _set.c_inputStickerSetID().vid().v;
_controller->session().api().request(MTPmessages_InstallStickerSet(
_set,
MTP_boolTrue()
)).done([=](const MTPmessages_StickerSetInstallResult &result) {
const auto controller = weak.get();
if (!controller) {
return;
}
if (result.type() == mtpc_messages_stickerSetInstallResultSuccess) {
Ui::Toast::Show(tr::lng_stickers_has_been_archived(tr::now));
const auto &session = controller->session();
auto &order = session.data().stickers().setsOrderRef();
const auto index = order.indexOf(setId);
if (index == -1) {
return;
}
order.removeAt(index);
session.local().writeInstalledStickers();
session.local().writeArchivedStickers();
session.data().stickers().notifyUpdated();
}
}).fail([](const MTP::Error &error) {
Ui::Toast::Show(Lang::Hard::ServerError());
}).send();
}
void StickerSetBox::updateTitleAndButtons() {
setTitle(_inner->title());
updateButtons();
@@ -260,8 +270,12 @@ void StickerSetBox::updateTitleAndButtons() {
void StickerSetBox::updateButtons() {
clearButtons();
if (_inner->loaded()) {
const auto isMasks = _inner->isMasksSet();
if (_inner->notInstalled()) {
addButton(tr::lng_stickers_add_pack(), [=] { addStickers(); });
auto addText = isMasks
? tr::lng_stickers_add_masks()
: tr::lng_stickers_add_pack();
addButton(std::move(addText), [=] { addStickers(); });
addButton(tr::lng_cancel(), [=] { closeBox(); });
if (!_inner->shortName().isEmpty()) {
@@ -276,7 +290,9 @@ void StickerSetBox::updateButtons() {
top->setClickedCallback([=] {
*menu = base::make_unique_q<Ui::PopupMenu>(top);
(*menu)->addAction(
tr::lng_stickers_share_pack(tr::now),
(isMasks
? tr::lng_stickers_share_masks
: tr::lng_stickers_share_pack)(tr::now),
share);
(*menu)->popup(QCursor::pos());
return true;
@@ -289,21 +305,25 @@ void StickerSetBox::updateButtons() {
copyStickersLink();
Ui::Toast::Show(tr::lng_stickers_copied(tr::now));
};
addButton(tr::lng_stickers_share_pack(), std::move(share));
auto shareText = isMasks
? tr::lng_stickers_share_masks()
: tr::lng_stickers_share_pack();
addButton(std::move(shareText), std::move(share));
addButton(tr::lng_cancel(), [=] { closeBox(); });
if (!_inner->shortName().isEmpty()) {
const auto top = addTopButton(st::infoTopBarMenu);
const auto archive = [=] {
archiveStickers();
closeBox();
_inner->archiveStickers();
};
const auto menu =
std::make_shared<base::unique_qptr<Ui::PopupMenu>>();
top->setClickedCallback([=] {
*menu = base::make_unique_q<Ui::PopupMenu>(top);
(*menu)->addAction(
tr::lng_stickers_archive_pack(tr::now),
isMasks
? tr::lng_masks_archive_pack(tr::now)
: tr::lng_stickers_archive_pack(tr::now),
archive);
(*menu)->popup(QCursor::pos());
return true;
@@ -328,6 +348,10 @@ StickerSetBox::Inner::Inner(
: RpWidget(parent)
, _controller(controller)
, _api(&_controller->session().mtp())
, _pathGradient(std::make_unique<Ui::PathShiftGradient>(
st::windowBgRipple,
st::windowBgOver,
[=] { update(); }))
, _input(set)
, _previewTimer([=] { showPreview(); }) {
set.match([&](const MTPDinputStickerSetID &data) {
@@ -457,6 +481,10 @@ rpl::producer<uint64> StickerSetBox::Inner::setInstalled() const {
return _setInstalled.events();
}
rpl::producer<uint64> StickerSetBox::Inner::setArchived() const {
return _setArchived.events();
}
rpl::producer<> StickerSetBox::Inner::updateControls() const {
return _updateControls.events();
}
@@ -467,13 +495,19 @@ rpl::producer<StickerSetBox::Error> StickerSetBox::Inner::errors() const {
void StickerSetBox::Inner::installDone(
const MTPmessages_StickerSetInstallResult &result) {
auto &sets = _controller->session().data().stickers().setsRef();
auto &stickers = _controller->session().data().stickers();
auto &sets = stickers.setsRef();
const auto isMasks = isMasksSet();
bool wasArchived = (_setFlags & MTPDstickerSet::Flag::f_archived);
const bool wasArchived = (_setFlags & MTPDstickerSet::Flag::f_archived);
if (wasArchived) {
auto index = _controller->session().data().stickers().archivedSetsOrderRef().indexOf(_setId);
const auto index = (isMasks
? stickers.archivedMaskSetsOrderRef()
: stickers.archivedSetsOrderRef()).indexOf(_setId);
if (index >= 0) {
_controller->session().data().stickers().archivedSetsOrderRef().removeAt(index);
(isMasks
? stickers.archivedMaskSetsOrderRef()
: stickers.archivedSetsOrderRef()).removeAt(index);
}
}
_setInstallDate = base::unixtime::now();
@@ -502,8 +536,10 @@ void StickerSetBox::Inner::installDone(
set->stickers = _pack;
set->emoji = _emoji;
auto &order = _controller->session().data().stickers().setsOrderRef();
int insertAtIndex = 0, currentIndex = order.indexOf(_setId);
auto &order = isMasks
? stickers.maskSetsOrderRef()
: stickers.setsOrderRef();
const auto insertAtIndex = 0, currentIndex = order.indexOf(_setId);
if (currentIndex != insertAtIndex) {
if (currentIndex > 0) {
order.removeAt(currentIndex);
@@ -515,8 +551,10 @@ void StickerSetBox::Inner::installDone(
if (customIt != sets.cend()) {
const auto custom = customIt->second.get();
for (const auto sticker : std::as_const(_pack)) {
int removeIndex = custom->stickers.indexOf(sticker);
if (removeIndex >= 0) custom->stickers.removeAt(removeIndex);
const int removeIndex = custom->stickers.indexOf(sticker);
if (removeIndex >= 0) {
custom->stickers.removeAt(removeIndex);
}
}
if (custom->stickers.isEmpty()) {
sets.erase(customIt);
@@ -524,14 +562,23 @@ void StickerSetBox::Inner::installDone(
}
if (result.type() == mtpc_messages_stickerSetInstallResultArchive) {
_controller->session().data().stickers().applyArchivedResult(
stickers.applyArchivedResult(
result.c_messages_stickerSetInstallResultArchive());
} else {
auto &storage = _controller->session().local();
if (wasArchived) {
_controller->session().local().writeArchivedStickers();
if (isMasks) {
storage.writeArchivedMasks();
} else {
storage.writeArchivedStickers();
}
}
_controller->session().local().writeInstalledStickers();
_controller->session().data().stickers().notifyUpdated();
if (isMasks) {
storage.writeInstalledMasks();
} else {
storage.writeInstalledStickers();
}
stickers.notifyUpdated();
}
_setInstalled.fire_copy(_setId);
}
@@ -652,6 +699,8 @@ void StickerSetBox::Inner::paintEvent(QPaintEvent *e) {
+ ((_elements.size() % kStickersPanelPerRow) ? 1 : 0);
int32 from = qFloor(e->rect().top() / st::stickersSize.height()), to = qFloor(e->rect().bottom() / st::stickersSize.height()) + 1;
_pathGradient->startFrame(0, width(), width() / 2);
for (int32 i = from; i < to; ++i) {
for (int32 j = 0; j < kStickersPanelPerRow; ++j) {
int32 index = i * kStickersPanelPerRow + j;
@@ -747,7 +796,8 @@ void StickerSetBox::Inner::paintSticker(
const auto &media = element.documentMedia;
media->checkStickerSmall();
if (document->sticker()->animated
const auto isAnimated = document->sticker()->animated;
if (isAnimated
&& !element.animated
&& media->loaded()) {
const_cast<Inner*>(this)->setupLottie(index);
@@ -755,7 +805,7 @@ void StickerSetBox::Inner::paintSticker(
auto w = 1;
auto h = 1;
if (element.animated && !document->dimensions.isEmpty()) {
if (isAnimated && !document->dimensions.isEmpty()) {
const auto request = Lottie::FrameRequest{ boundingBoxSize() * cIntRetinaFactor() };
const auto size = request.size(document->dimensions, true) / cIntRetinaFactor();
w = std::max(size.width(), 1);
@@ -767,6 +817,7 @@ void StickerSetBox::Inner::paintSticker(
h = std::max(qRound(coef * document->dimensions.height()), 1);
}
QPoint ppos = position + QPoint((st::stickersSize.width() - w) / 2, (st::stickersSize.height() - h) / 2);
if (element.animated && element.animated->ready()) {
const auto frame = element.animated->frame();
p.drawImage(
@@ -779,6 +830,12 @@ void StickerSetBox::Inner::paintSticker(
ppos,
width(),
image->pix(w, h));
} else {
ChatHelpers::PaintStickerThumbnailPath(
p,
media.get(),
QRect(ppos, QSize(w, h)),
_pathGradient.get());
}
}
@@ -820,12 +877,7 @@ QString StickerSetBox::Inner::shortName() const {
}
void StickerSetBox::Inner::install() {
if (isMasksSet()) {
_controller->show(
Box<InformBox>(tr::lng_stickers_masks_pack(tr::now)),
Ui::LayerOption::KeepOther);
return;
} else if (_installRequest) {
if (_installRequest) {
return;
}
_installRequest = _api.request(MTPmessages_InstallStickerSet(
@@ -838,4 +890,17 @@ void StickerSetBox::Inner::install() {
}).send();
}
void StickerSetBox::Inner::archiveStickers() {
_api.request(MTPmessages_InstallStickerSet(
_input,
MTP_boolTrue()
)).done([=](const MTPmessages_StickerSetInstallResult &result) {
if (result.type() == mtpc_messages_stickerSetInstallResultSuccess) {
_setArchived.fire_copy(_setId);
}
}).fail([](const MTP::Error &error) {
Ui::Toast::Show(Lang::Hard::ServerError());
}).send();
}
StickerSetBox::Inner::~Inner() = default;

View File

@@ -46,7 +46,6 @@ private:
void updateButtons();
void addStickers();
void copyStickersLink();
void archiveStickers();
void handleError(Error error);
const not_null<Window::SessionController*> _controller;

View File

@@ -90,7 +90,7 @@ public:
void saveGroupSet();
void rebuild();
void rebuild(bool masks);
void updateSize(int newWidth = 0);
void updateRows(); // refresh only pack cover stickers
bool appendSet(not_null<StickersSet*> set);
@@ -152,6 +152,7 @@ private:
~Row();
bool isRecentSet() const;
bool isMasksSet() const;
const not_null<StickersSet*> set;
DocumentData *sticker = nullptr;
@@ -239,7 +240,8 @@ private:
const not_null<Window::SessionController*> _controller;
MTP::Sender _api;
Section _section;
const Section _section;
const bool _isInstalled;
int32 _rowHeight;
@@ -351,7 +353,7 @@ void StickersBox::Tab::returnWidget(object_ptr<Inner> widget) {
Assert(_widget == _weak);
}
StickersBox::Inner *StickersBox::Tab::widget() {
StickersBox::Inner *StickersBox::Tab::widget() const {
return _weak;
}
@@ -366,7 +368,8 @@ void StickersBox::Tab::saveScrollTop() {
StickersBox::StickersBox(
QWidget*,
not_null<Window::SessionController*> controller,
Section section)
Section section,
bool masks)
: _controller(controller)
, _api(&controller->session().mtp())
, _tabs(this, st::stickersTabs)
@@ -374,9 +377,11 @@ StickersBox::StickersBox(
this,
controller->session().data().stickers().featuredSetsUnreadCountValue())
, _section(section)
, _installed(0, this, controller, Section::Installed)
, _featured(1, this, controller, Section::Featured)
, _archived(2, this, controller, Section::Archived) {
, _isMasks(masks)
, _installed(_isMasks ? Tab() : Tab(0, this, controller, Section::Installed))
, _masks(_isMasks ? Tab(0, this, controller, Section::Masks) : Tab())
, _featured(_isMasks ? Tab() : Tab(1, this, controller, Section::Featured))
, _archived((_isMasks ? 1 : 2), this, controller, Section::Archived) {
_tabs->setRippleTopRoundRadius(st::boxRadius);
}
@@ -387,6 +392,7 @@ StickersBox::StickersBox(
: _controller(controller)
, _api(&controller->session().mtp())
, _section(Section::Installed)
, _isMasks(false)
, _installed(0, this, controller, megagroup)
, _megagroupSet(megagroup) {
_installed.widget()->scrollsToY(
@@ -402,6 +408,7 @@ StickersBox::StickersBox(
: _controller(controller)
, _api(&controller->session().mtp())
, _section(Section::Attached)
, _isMasks(false)
, _attached(0, this, controller, Section::Attached)
, _attachedSets(attachedSets) {
}
@@ -447,7 +454,7 @@ void StickersBox::getArchivedDone(
}
auto &stickers = result.c_messages_archivedStickers();
auto &archived = session().data().stickers().archivedSetsOrderRef();
auto &archived = archivedSetsOrderRef();
if (offsetId) {
auto index = archived.indexOf(offsetId);
if (index >= 0) {
@@ -514,7 +521,11 @@ void StickersBox::getArchivedDone(
void StickersBox::prepare() {
if (_section == Section::Installed) {
if (_tabs) {
session().local().readArchivedStickers();
if (_isMasks) {
session().local().readArchivedMasks();
} else {
session().local().readArchivedStickers();
}
} else {
setTitle(tr::lng_stickers_group_set());
}
@@ -524,7 +535,7 @@ void StickersBox::prepare() {
setTitle(tr::lng_stickers_attached_sets());
}
if (_tabs) {
if (session().data().stickers().archivedSetsOrder().isEmpty()) {
if (archivedSetsOrder().isEmpty()) {
preloadArchivedSets();
}
setNoContentMargin(true);
@@ -534,21 +545,33 @@ void StickersBox::prepare() {
lifetime());
refreshTabs();
}
if (_installed.widget() && _section != Section::Installed) _installed.widget()->hide();
if (_featured.widget() && _section != Section::Featured) _featured.widget()->hide();
if (_archived.widget() && _section != Section::Archived) _archived.widget()->hide();
if (_attached.widget() && _section != Section::Attached) _attached.widget()->hide();
if (_installed.widget() && _section != Section::Installed) {
_installed.widget()->hide();
}
if (_masks.widget() && _section != Section::Masks) {
_masks.widget()->hide();
}
if (_featured.widget() && _section != Section::Featured) {
_featured.widget()->hide();
}
if (_archived.widget() && _section != Section::Archived) {
_archived.widget()->hide();
}
if (_attached.widget() && _section != Section::Attached) {
_attached.widget()->hide();
}
const auto installCallback = [=](uint64 setId) { installSet(setId); };
if (_featured.widget()) {
_featured.widget()->setInstallSetCallback([this](uint64 setId) { installSet(setId); });
_featured.widget()->setInstallSetCallback(installCallback);
}
if (_archived.widget()) {
_archived.widget()->setInstallSetCallback([this](uint64 setId) { installSet(setId); });
_archived.widget()->setLoadMoreCallback([this] { loadMoreArchived(); });
_archived.widget()->setInstallSetCallback(installCallback);
_archived.widget()->setLoadMoreCallback([=] { loadMoreArchived(); });
}
if (_attached.widget()) {
_attached.widget()->setInstallSetCallback([this](uint64 setId) { installSet(setId); });
_attached.widget()->setLoadMoreCallback([this] { showAttachedStickers(); });
_attached.widget()->setInstallSetCallback(installCallback);
_attached.widget()->setLoadMoreCallback([=] { showAttachedStickers(); });
}
if (_megagroupSet) {
@@ -565,6 +588,8 @@ void StickersBox::prepare() {
if (_section == Section::Installed) {
_tab = &_installed;
} else if (_section == Section::Masks) {
_tab = &_masks;
} else if (_section == Section::Archived) {
_tab = &_archived;
} else if (_section == Section::Attached) {
@@ -580,18 +605,21 @@ void StickersBox::prepare() {
[this] { handleStickersUpdated(); },
lifetime());
session().api().updateStickers();
session().api().updateMasks();
if (_installed.widget()) {
_installed.widget()->draggingScrollDelta(
) | rpl::start_with_next([=](int delta) {
scrollByDraggingDelta(delta);
}, _installed.widget()->lifetime());
if (!_megagroupSet) {
boxClosing() | rpl::start_with_next([=] {
saveChanges();
}, lifetime());
for (const auto &widget : { _installed.widget(), _masks.widget() }) {
if (widget) {
widget->draggingScrollDelta(
) | rpl::start_with_next([=](int delta) {
scrollByDraggingDelta(delta);
}, widget->lifetime());
}
}
if (!_megagroupSet) {
boxClosing() | rpl::start_with_next([=] {
saveChanges();
}, lifetime());
}
if (_tabs) {
_tabs->raise();
@@ -601,28 +629,41 @@ void StickersBox::prepare() {
}
void StickersBox::refreshTabs() {
if (!_tabs) return;
if (!_tabs) {
return;
}
auto &stickers = session().data().stickers();
_tabIndices.clear();
auto sections = QStringList();
sections.push_back(tr::lng_stickers_installed_tab(tr::now).toUpper());
_tabIndices.push_back(Section::Installed);
if (!session().data().stickers().featuredSetsOrder().isEmpty()) {
auto sections = std::vector<QString>();
if (_installed.widget()) {
sections.push_back(tr::lng_stickers_installed_tab(tr::now).toUpper());
_tabIndices.push_back(Section::Installed);
}
if (_masks.widget()) {
sections.push_back(tr::lng_stickers_masks_tab(tr::now).toUpper());
_tabIndices.push_back(Section::Masks);
}
if (!stickers.featuredSetsOrder().isEmpty() && _featured.widget()) {
sections.push_back(tr::lng_stickers_featured_tab(tr::now).toUpper());
_tabIndices.push_back(Section::Featured);
}
if (!session().data().stickers().archivedSetsOrder().isEmpty()) {
if (!archivedSetsOrder().isEmpty() && _archived.widget()) {
sections.push_back(tr::lng_stickers_archived_tab(tr::now).toUpper());
_tabIndices.push_back(Section::Archived);
}
_tabs->setSections(sections);
if ((_tab == &_archived && !_tabIndices.contains(Section::Archived))
|| (_tab == &_featured && !_tabIndices.contains(Section::Featured))) {
|| (_tab == &_featured && !_tabIndices.contains(Section::Featured))
|| (_tab == &_masks && !_tabIndices.contains(Section::Masks))) {
switchTab();
} else if (_tab == &_archived) {
_tabs->setActiveSectionFast(_tabIndices.indexOf(Section::Archived));
} else if (_tab == &_featured) {
_tabs->setActiveSectionFast(_tabIndices.indexOf(Section::Featured));
} else if (_tab == &_masks) {
_tabs->setActiveSectionFast(_tabIndices.indexOf(Section::Masks));
}
updateTabsGeometry();
}
@@ -635,7 +676,7 @@ void StickersBox::loadMoreArchived() {
}
uint64 lastId = 0;
const auto &order = session().data().stickers().archivedSetsOrder();
const auto &order = archivedSetsOrder();
const auto &sets = session().data().stickers().sets();
for (auto setIt = order.cend(), e = order.cbegin(); setIt != e;) {
--setIt;
@@ -647,8 +688,11 @@ void StickersBox::loadMoreArchived() {
}
}
}
const auto flags = _isMasks
? MTPmessages_GetArchivedStickers::Flag::f_masks
: MTPmessages_GetArchivedStickers::Flags(0);
_archivedRequestId = _api.request(MTPmessages_GetArchivedStickers(
MTP_flags(0),
MTP_flags(flags),
MTP_long(lastId),
MTP_int(kArchivedLimitPerPage)
)).done([=](const MTPmessages_ArchivedStickers &result) {
@@ -674,13 +718,15 @@ void StickersBox::paintEvent(QPaintEvent *e) {
void StickersBox::updateTabsGeometry() {
if (!_tabs) return;
_tabs->resizeToWidth(_tabIndices.size() * width() / 3);
const auto maxTabs = _isMasks ? 2 : 3;
_tabs->resizeToWidth(_tabIndices.size() * width() / maxTabs);
_unreadBadge->setVisible(_tabIndices.contains(Section::Featured));
setInnerTopSkip(getTopSkip());
auto featuredLeft = width() / 3;
auto featuredRight = 2 * width() / 3;
auto featuredLeft = width() / maxTabs;
auto featuredRight = 2 * width() / maxTabs;
auto featuredTextWidth = st::stickersTabs.labelStyle.font->width(tr::lng_stickers_featured_tab(tr::now).toUpper());
auto featuredTextRight = featuredLeft + (featuredRight - featuredLeft - featuredTextWidth) / 2 + featuredTextWidth;
auto unreadBadgeLeft = featuredTextRight - st::stickersFeaturedBadgeSkip;
@@ -712,6 +758,9 @@ void StickersBox::switchTab() {
} else if (newSection == Section::Archived) {
newTab = &_archived;
requestArchivedSets();
} else if (newSection == Section::Masks) {
newTab = &_masks;
session().api().updateMasks();
}
if (_tab == newTab) {
onScrollToY(0);
@@ -744,7 +793,7 @@ void StickersBox::switchTab() {
_slideAnimation = std::make_unique<Ui::SlideAnimation>();
_slideAnimation->setSnapshots(std::move(wasCache), std::move(nowCache));
auto slideLeft = wasIndex > nowIndex;
_slideAnimation->start(slideLeft, [this] { update(); }, st::slideDuration);
_slideAnimation->start(slideLeft, [=] { update(); }, st::slideDuration);
setInnerVisible(false);
setFocus();
@@ -758,6 +807,16 @@ QPixmap StickersBox::grabContentCache() {
return result;
}
std::array<StickersBox::Inner*, 5> StickersBox::widgets() const {
return {
_installed.widget(),
_featured.widget(),
_archived.widget(),
_attached.widget(),
_masks.widget()
};
}
void StickersBox::installSet(uint64 setId) {
const auto &sets = session().data().stickers().sets();
const auto it = sets.find(setId);
@@ -769,10 +828,11 @@ void StickersBox::installSet(uint64 setId) {
const auto set = it->second.get();
if (_localRemoved.contains(setId)) {
_localRemoved.removeOne(setId);
if (_installed.widget()) _installed.widget()->setRemovedSets(_localRemoved);
if (_featured.widget()) _featured.widget()->setRemovedSets(_localRemoved);
if (_archived.widget()) _archived.widget()->setRemovedSets(_localRemoved);
if (_attached.widget()) _attached.widget()->setRemovedSets(_localRemoved);
for (const auto &widget : widgets()) {
if (widget) {
widget->setRemovedSets(_localRemoved);
}
}
}
if (!(set->flags & MTPDstickerSet::Flag::f_installed_date)
|| (set->flags & MTPDstickerSet::Flag::f_archived)) {
@@ -811,8 +871,11 @@ void StickersBox::preloadArchivedSets() {
return;
}
if (!_archivedRequestId) {
const auto flags = _isMasks
? MTPmessages_GetArchivedStickers::Flag::f_masks
: MTPmessages_GetArchivedStickers::Flags(0);
_archivedRequestId = _api.request(MTPmessages_GetArchivedStickers(
MTP_flags(0),
MTP_flags(flags),
MTP_long(0),
MTP_int(kArchivedLimitFirstRequest)
)).done([=](const MTPmessages_ArchivedStickers &result) {
@@ -828,7 +891,7 @@ void StickersBox::requestArchivedSets() {
}
const auto &sets = session().data().stickers().sets();
const auto &order = session().data().stickers().archivedSetsOrder();
const auto &order = archivedSetsOrder();
for (const auto setId : order) {
auto it = sets.find(setId);
if (it != sets.cend()) {
@@ -850,19 +913,22 @@ void StickersBox::resizeEvent(QResizeEvent *e) {
if (_titleShadow) {
_titleShadow->setGeometry(0, 0, width(), st::lineWidth);
}
if (_installed.widget()) _installed.widget()->resize(width(), _installed.widget()->height());
if (_featured.widget()) _featured.widget()->resize(width(), _featured.widget()->height());
if (_archived.widget()) _archived.widget()->resize(width(), _archived.widget()->height());
if (_attached.widget()) _attached.widget()->resize(width(), _attached.widget()->height());
for (const auto &widget : widgets()) {
if (widget) {
widget->resize(width(), widget->height());
}
}
}
void StickersBox::handleStickersUpdated() {
if (_section == Section::Installed || _section == Section::Featured) {
if (_section == Section::Installed
|| _section == Section::Featured
|| _section == Section::Masks) {
rebuildList();
} else {
_tab->widget()->updateRows();
}
if (session().data().stickers().archivedSetsOrder().isEmpty()) {
if (archivedSetsOrder().isEmpty()) {
preloadArchivedSets();
} else {
refreshTabs();
@@ -870,28 +936,55 @@ void StickersBox::handleStickersUpdated() {
}
void StickersBox::rebuildList(Tab *tab) {
if (_section == Section::Attached) return;
if (!tab) tab = _tab;
if (_section == Section::Attached) {
return;
}
if (!tab) {
tab = _tab;
}
if (tab == &_installed) {
if ((tab == &_installed) || (tab == &_masks)) {
_localOrder = tab->widget()->getFullOrder();
_localRemoved = tab->widget()->getRemovedSets();
}
tab->widget()->rebuild();
if (tab == &_installed) {
tab->widget()->rebuild(_isMasks);
if ((tab == &_installed) || (tab == &_masks)) {
tab->widget()->setFullOrder(_localOrder);
}
tab->widget()->setRemovedSets(_localRemoved);
}
void StickersBox::saveChanges() {
const auto installed = _installed.widget();
const auto masks = _masks.widget();
// Make sure that our changes in other tabs are applied in the Installed tab.
rebuildList(&_installed);
if (installed) {
rebuildList(&_installed);
}
if (masks) {
rebuildList(&_masks);
}
if (_someArchivedLoaded) {
session().local().writeArchivedStickers();
if (_isMasks) {
session().local().writeArchivedMasks();
} else {
session().local().writeArchivedStickers();
}
}
if (installed) {
session().api().saveStickerSets(
installed->getOrder(),
installed->getRemovedSets(),
false);
}
if (masks) {
session().api().saveStickerSets(
masks->getOrder(),
masks->getRemovedSets(),
true);
}
session().api().saveStickerSets(_installed.widget()->getOrder(), _installed.widget()->getRemovedSets());
}
void StickersBox::setInnerFocus() {
@@ -900,6 +993,18 @@ void StickersBox::setInnerFocus() {
}
}
const Data::StickersSetsOrder &StickersBox::archivedSetsOrder() const {
return !_isMasks
? session().data().stickers().archivedSetsOrder()
: session().data().stickers().archivedMaskSetsOrder();
}
Data::StickersSetsOrder &StickersBox::archivedSetsOrderRef() {
return !_isMasks
? session().data().stickers().archivedSetsOrderRef()
: session().data().stickers().archivedMaskSetsOrderRef();
}
StickersBox::~StickersBox() = default;
StickersBox::Inner::Row::Row(
@@ -932,7 +1037,12 @@ StickersBox::Inner::Row::Row(
StickersBox::Inner::Row::~Row() = default;
bool StickersBox::Inner::Row::isRecentSet() const {
return (set->id == Data::Stickers::CloudRecentSetId);
return (set->id == Data::Stickers::CloudRecentSetId)
|| (set->id == Data::Stickers::CloudRecentAttachedSetId);
}
bool StickersBox::Inner::Row::isMasksSet() const {
return (set->flags & MTPDstickerSet::Flag::f_masks);
}
StickersBox::Inner::Inner(
@@ -943,6 +1053,7 @@ StickersBox::Inner::Inner(
, _controller(controller)
, _api(&_controller->session().mtp())
, _section(section)
, _isInstalled(_section == Section::Installed || _section == Section::Masks)
, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom())
, _shiftingAnimation([=](crl::time now) {
return shiftingAnimationCallback(now);
@@ -963,6 +1074,7 @@ StickersBox::Inner::Inner(
, _controller(controller)
, _api(&_controller->session().mtp())
, _section(StickersBox::Section::Installed)
, _isInstalled(_section == Section::Installed || _section == Section::Masks)
, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom())
, _shiftingAnimation([=](crl::time now) {
return shiftingAnimationCallback(now);
@@ -1087,8 +1199,10 @@ QRect StickersBox::Inner::relativeButtonRect(bool removeButton) const {
auto buttonh = st::stickersRemove.height;
auto buttonshift = st::stickersRemoveSkip;
if (!removeButton) {
auto &st = (_section == Section::Installed) ? st::stickersUndoRemove : st::stickersTrendingAdd;
auto textWidth = (_section == Section::Installed) ? _undoWidth : _addWidth;
const auto &st = _isInstalled
? st::stickersUndoRemove
: st::stickersTrendingAdd;
const auto textWidth = _isInstalled ? _undoWidth : _addWidth;
buttonw = textWidth - st.width;
buttonh = st.height;
buttonshift = 0;
@@ -1117,7 +1231,7 @@ void StickersBox::Inner::paintRow(Painter &p, not_null<Row*> row, int index) {
}
}
if (_section == Section::Installed) {
if (_isInstalled) {
if (index >= 0 && index == _above) {
auto current = _aboveShadowFadeOpacity.current();
if (_started >= 0) {
@@ -1144,13 +1258,13 @@ void StickersBox::Inner::paintRow(Painter &p, not_null<Row*> row, int index) {
paintFakeButton(p, row, index);
}
if (row->removed && _section == Section::Installed) {
if (row->removed && _isInstalled) {
p.setOpacity(st::stickersRowDisabledOpacity);
}
auto stickerx = st::contactsPadding.left();
if (!_megagroupSet && _section == Section::Installed) {
if (!_megagroupSet && _isInstalled) {
stickerx += st::stickersReorderIcon.width() + st::stickersReorderSkip;
if (!row->isRecentSet()) {
st::stickersReorderIcon.paint(p, st::contactsPadding.left(), (_rowHeight - st::stickersReorderIcon.height()) / 2, width());
@@ -1181,7 +1295,11 @@ void StickersBox::Inner::paintRow(Painter &p, not_null<Row*> row, int index) {
}
}
auto statusText = (row->count > 0) ? tr::lng_stickers_count(tr::now, lt_count, row->count) : tr::lng_contacts_loading(tr::now);
const auto statusText = (row->count == 0)
? tr::lng_contacts_loading(tr::now)
: row->isMasksSet()
? tr::lng_masks_count(tr::now, lt_count, row->count)
: tr::lng_stickers_count(tr::now, lt_count, row->count);
p.setFont(st::contactsStatusFont);
p.setPen(st::contactsStatusFg);
@@ -1281,7 +1399,7 @@ void StickersBox::Inner::updateRowThumbnail(not_null<Row*> row) {
Unexpected("StickersBox::Inner::updateRowThumbnail: row not found");
}();
const auto left = st::contactsPadding.left()
+ ((!_megagroupSet && _section == Section::Installed)
+ ((!_megagroupSet && _isInstalled)
? st::stickersReorderIcon.width() + st::stickersReorderSkip
: 0);
update(
@@ -1292,9 +1410,9 @@ void StickersBox::Inner::updateRowThumbnail(not_null<Row*> row) {
}
void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> row, int index) {
auto removeButton = (_section == Section::Installed && !row->removed);
auto removeButton = (_isInstalled && !row->removed);
auto rect = relativeButtonRect(removeButton);
if (_section != Section::Installed && row->installed && !row->archived && !row->removed) {
if (!_isInstalled && row->installed && !row->archived && !row->removed) {
// Checkbox after installed from Trending or Archived.
int checkx = width() - (st::contactsPadding.right() + st::contactsCheckPosition.x() + (rect.width() + st::stickersFeaturedInstalled.width()) / 2);
int checky = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersFeaturedInstalled.height()) / 2;
@@ -1317,10 +1435,12 @@ void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> row, int ind
} else {
// Round button ADD when not installed from Trending or Archived.
// Or round button UNDO after disabled from Installed.
auto &st = (_section == Section::Installed) ? st::stickersUndoRemove : st::stickersTrendingAdd;
auto textWidth = (_section == Section::Installed) ? _undoWidth : _addWidth;
auto &text = (_section == Section::Installed) ? _undoText : _addText;
auto &textBg = selected ? st.textBgOver : st.textBg;
const auto &st = _isInstalled
? st::stickersUndoRemove
: st::stickersTrendingAdd;
const auto textWidth = _isInstalled ? _undoWidth : _addWidth;
const auto &text = _isInstalled ? _undoText : _addText;
const auto &textBg = selected ? st.textBgOver : st.textBg;
Ui::FillRoundRect(p, myrtlrect(rect), textBg, ImageRoundRadius::Small);
if (row->ripple) {
row->ripple->paint(p, rect.x(), rect.y(), width());
@@ -1345,7 +1465,7 @@ void StickersBox::Inner::mousePressEvent(QMouseEvent *e) {
setActionDown(_actionSel);
update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight);
} else if (auto selectedIndex = std::get_if<int>(&_selected)) {
if (_section == Section::Installed && !_rows[*selectedIndex]->isRecentSet() && _inDragArea) {
if (_isInstalled && !_rows[*selectedIndex]->isRecentSet() && _inDragArea) {
_above = _dragging = _started = *selectedIndex;
_dragStart = mapFromGlobal(_mouse);
}
@@ -1367,9 +1487,9 @@ void StickersBox::Inner::setActionDown(int newActionDown) {
if (_actionDown >= 0 && _actionDown < _rows.size()) {
update(0, _itemsTop + _actionDown * _rowHeight, width(), _rowHeight);
const auto row = _rows[_actionDown].get();
auto removeButton = (_section == Section::Installed && !row->removed);
auto removeButton = (_isInstalled && !row->removed);
if (!row->ripple) {
if (_section == Section::Installed) {
if (_isInstalled) {
if (row->removed) {
auto rippleSize = QSize(_undoWidth - st::stickersUndoRemove.width, st::stickersUndoRemove.height);
auto rippleMask = Ui::RippleAnimation::roundRectMask(rippleSize, st::roundRadiusSmall);
@@ -1512,14 +1632,14 @@ void StickersBox::Inner::updateSelected() {
selected = selectedIndex;
local.setY(local.y() - _itemsTop - selectedIndex * _rowHeight);
const auto row = _rows[selectedIndex].get();
if (!_megagroupSet && (_section == Section::Installed || !row->installed || row->archived || row->removed)) {
auto removeButton = (_section == Section::Installed && !row->removed);
if (!_megagroupSet && (_isInstalled || !row->installed || row->archived || row->removed)) {
auto removeButton = (_isInstalled && !row->removed);
auto rect = myrtlrect(relativeButtonRect(removeButton));
actionSel = rect.contains(local) ? selectedIndex : -1;
} else {
actionSel = -1;
}
if (!_megagroupSet && _section == Section::Installed && !row->isRecentSet()) {
if (!_megagroupSet && _isInstalled && !row->isRecentSet()) {
auto dragAreaWidth = st::contactsPadding.left() + st::stickersReorderIcon.width() + st::stickersReorderSkip;
auto dragArea = myrtlrect(0, 0, dragAreaWidth, _rowHeight);
inDragArea = dragArea.contains(local);
@@ -1543,7 +1663,7 @@ void StickersBox::Inner::updateSelected() {
void StickersBox::Inner::updateCursor() {
setCursor(_inDragArea
? style::cur_sizeall
: (!_megagroupSet && _section == Section::Installed)
: (!_megagroupSet && _isInstalled)
? ((_actionSel >= 0 && (_actionDown < 0 || _actionDown == _actionSel))
? style::cur_pointer
: style::cur_default)
@@ -1568,7 +1688,7 @@ void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
_mouse = e->globalPos();
updateSelected();
if (_actionDown == _actionSel && _actionSel >= 0) {
if (_section == Section::Installed) {
if (_isInstalled) {
setRowRemoved(_actionDown, !_rows[_actionDown]->removed);
} else if (_installSetCallback) {
_installSetCallback(_rows[_actionDown]->set->id);
@@ -1623,7 +1743,8 @@ void StickersBox::Inner::saveGroupSet() {
: 0;
if (newId != oldId) {
session().api().setGroupStickerSet(_megagroupSet, _megagroupSetInput);
session().api().stickerSetInstalled(Data::Stickers::MegagroupSetId);
session().data().stickers().notifyStickerSetInstalled(
Data::Stickers::MegagroupSetId);
}
}
@@ -1840,7 +1961,7 @@ void StickersBox::Inner::rebuildMegagroupSet() {
}
}
void StickersBox::Inner::rebuild() {
void StickersBox::Inner::rebuild(bool masks) {
_itemsTop = st::membersMarginTop;
if (_megagroupSet) {
@@ -1859,10 +1980,14 @@ void StickersBox::Inner::rebuild() {
return session().data().stickers().featuredSetsOrder();
}
return result;
} else if (_section == Section::Masks) {
return session().data().stickers().maskSetsOrder();
} else if (_section == Section::Featured) {
return session().data().stickers().featuredSetsOrder();
}
return session().data().stickers().archivedSetsOrder();
return masks
? session().data().stickers().archivedMaskSetsOrder()
: session().data().stickers().archivedSetsOrder();
})();
_rows.reserve(order.size() + 1);
_shiftingStartTimes.reserve(order.size() + 1);
@@ -1874,8 +1999,10 @@ void StickersBox::Inner::rebuild() {
? tr::lng_stickers_group_from_featured(tr::now)
: tr::lng_stickers_group_from_your(tr::now));
updateControlsGeometry();
} else if (_section == Section::Installed) {
auto cloudIt = sets.find(Data::Stickers::CloudRecentSetId);
} else if (_isInstalled) {
const auto cloudIt = sets.find((_section == Section::Masks)
? Data::Stickers::CloudRecentAttachedSetId
: Data::Stickers::CloudRecentSetId); // Section::Installed.
if (cloudIt != sets.cend() && !cloudIt->second->stickers.isEmpty()) {
rebuildAppendSet(cloudIt->second.get(), maxNameWidth);
}
@@ -1900,7 +2027,7 @@ void StickersBox::Inner::rebuild() {
void StickersBox::Inner::setMegagroupSelectedSet(const MTPInputStickerSet &set) {
_megagroupSetInput = set;
rebuild();
rebuild(false);
_scrollsToY.fire(0);
updateSelected();
}
@@ -1944,7 +2071,7 @@ void StickersBox::Inner::updateRows() {
auto wasInstalled = row->installed;
auto wasArchived = row->archived;
fillSetFlags(set, &row->installed, &row->official, &row->unread, &row->archived);
if (_section == Section::Installed) {
if (_isInstalled) {
row->archived = false;
}
if (row->installed != wasInstalled || row->archived != wasArchived) {
@@ -1969,11 +2096,11 @@ bool StickersBox::Inner::appendSet(not_null<StickersSet*> set) {
int StickersBox::Inner::countMaxNameWidth() const {
int namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left();
if (!_megagroupSet && _section == Section::Installed) {
if (!_megagroupSet && _isInstalled) {
namex += st::stickersReorderIcon.width() + st::stickersReorderSkip;
}
int namew = st::boxWideWidth - namex - st::contactsPadding.right() - st::contactsCheckPosition.x();
if (_section == Section::Installed) {
if (_isInstalled) {
if (!_megagroupSet) {
namew -= _undoWidth - st::stickersUndoRemove.width;
}
@@ -1993,7 +2120,7 @@ void StickersBox::Inner::rebuildAppendSet(
if (set->id != Data::Stickers::CloudRecentSetId) {
fillSetFlags(set, &installed, &official, &unread, &archived);
}
if (_section == Section::Installed && archived) {
if (_isInstalled && archived) {
return;
}

View File

@@ -56,12 +56,14 @@ public:
Featured,
Archived,
Attached,
Masks,
};
StickersBox(
QWidget*,
not_null<Window::SessionController*> controller,
Section section);
Section section,
bool masks = false);
StickersBox(
QWidget*,
not_null<Window::SessionController*> controller,
@@ -94,7 +96,7 @@ private:
object_ptr<Inner> takeWidget();
void returnWidget(object_ptr<Inner> widget);
[[nodiscard]] Inner *widget();
[[nodiscard]] Inner *widget() const;
[[nodiscard]] int index() const;
void saveScrollTop();
@@ -103,7 +105,7 @@ private:
}
private:
int _index = 0;
const int _index = 0;
object_ptr<Inner> _widget = { nullptr };
QPointer<Inner> _weak;
int _scrollTop = 0;
@@ -132,6 +134,11 @@ private:
uint64 offsetId);
void showAttachedStickers();
const Data::StickersSetsOrder &archivedSetsOrder() const;
Data::StickersSetsOrder &archivedSetsOrderRef();
std::array<Inner*, 5> widgets() const;
const not_null<Window::SessionController*> _controller;
MTP::Sender _api;
@@ -142,8 +149,10 @@ private:
object_ptr<CounterWidget> _unreadBadge = { nullptr };
Section _section;
const bool _isMasks;
Tab _installed;
Tab _masks;
Tab _featured;
Tab _archived;
Tab _attached;

View File

@@ -490,6 +490,7 @@ groupCallMenu: Menu(defaultMenu) {
itemFgShortcutDisabled: groupCallMemberNotJoinedStatus;
separatorFg: groupCallMenuBgOver;
separatorPadding: margins(0px, 4px, 0px, 4px);
arrow: icon {{ "dropdown_submenu_arrow", groupCallMemberNotJoinedStatus }};
@@ -513,6 +514,16 @@ groupCallPopupMenu: PopupMenu(defaultPopupMenu) {
menu: groupCallMenu;
animation: groupCallPanelAnimation;
}
groupCallPopupMenuWithVolume: PopupMenu(groupCallPopupMenu) {
scrollPadding: margins(0px, 3px, 0px, 8px);
menu: Menu(groupCallMenu) {
widthMin: 210px;
}
}
groupCallPopupVolumeMenu: Menu(groupCallMenu) {
widthMin: 210px;
itemBgOver: groupCallMenuBg;
}
groupCallRecordingTimerPadding: margins(0px, 4px, 0px, 4px);
groupCallRecordingTimerFont: font(12px);
@@ -1071,13 +1082,17 @@ groupCallMuteCrossLine: CrossLineAnimation {
groupCallMenuSpeakerArcsSkip: 1px;
groupCallMenuVolumeSkip: 5px;
groupCallMenuVolumePadding: margins(17px, 6px, 17px, 5px);
groupCallMenuVolumeMargin: margins(55px, 0px, 15px, 0px);
groupCallMenuVolumeSlider: MediaSlider(defaultContinuousSlider) {
activeFg: groupCallMembersFg;
inactiveFg: groupCallMemberInactiveIcon;
inactiveFg: groupCallMembersBgOver;
activeFgOver: groupCallMembersFg;
inactiveFgOver: groupCallMemberInactiveIcon;
activeFgDisabled: groupCallMemberInactiveIcon;
receivedTillFg: groupCallMemberInactiveIcon;
inactiveFgOver: groupCallMembersBgOver;
activeFgDisabled: groupCallMembersBgOver;
receivedTillFg: groupCallMembersBgOver;
width: 7px;
seekSize: size(7px, 7px);
}
groupCallSpeakerArcsAnimation: ArcsAnimation {
@@ -1251,5 +1266,16 @@ groupCallNiceTooltipLabel: FlatLabel(defaultImportantTooltipLabel) {
linkFontOver: font(11px underline);
}
}
groupCallStickedTooltip: ImportantTooltip(groupCallNiceTooltip) {
padding: margins(10px, 1px, 6px, 3px);
}
groupCallStickedTooltipClose: IconButton(defaultIconButton) {
width: 20px;
height: 20px;
iconPosition: point(4px, 3px);
icon: icon {{ "calls/video_tooltip", importantTooltipFg }};
iconOver: icon {{ "calls/video_tooltip", importantTooltipFg }};
ripple: emptyRippleAnimation;
}
groupCallNiceTooltipTop: 4px;
groupCallPaused: icon {{ "calls/video_large_paused", groupCallVideoTextFg }};

View File

@@ -42,7 +42,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "platform/platform_specific.h"
#include "base/platform/base_platform_info.h"
#include "window/main_window.h"
#include "media/view/media_view_pip.h" // Utilities for frame rotation.
#include "app.h"
#include "webrtc/webrtc_video_track.h"
#include "styles/style_calls.h"
@@ -51,16 +50,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtWidgets/QDesktopWidget>
#include <QtWidgets/QApplication>
#include <QtGui/QWindow>
#include <QtCore/QTimer>
namespace Calls {
Panel::Panel(not_null<Call*> call)
: _call(call)
, _user(call->user())
, _window(createWindow())
#ifndef Q_OS_MAC
, _controls(std::make_unique<Ui::Platform::TitleControls>(
_window->body(),
widget(),
st::callTitle,
[=](bool maximized) { toggleFullScreen(maximized); }))
#endif // !Q_OS_MAC
@@ -86,46 +85,27 @@ Panel::Panel(not_null<Call*> call)
Panel::~Panel() = default;
std::unique_ptr<Ui::Window> Panel::createWindow() {
auto result = std::make_unique<Ui::Window>();
const auto capabilities = Ui::GL::CheckCapabilities(result.get());
const auto use = Platform::IsMac()
? true
: Platform::IsWindows()
? capabilities.supported
: capabilities.transparency;
LOG(("OpenGL: %1 (Incoming)").arg(Logs::b(use)));
_backend = use ? Ui::GL::Backend::OpenGL : Ui::GL::Backend::Raster;
if (use) {
return result;
}
// We have to create a new window, if OpenGL initialization failed.
return std::make_unique<Ui::Window>();
}
bool Panel::isActive() const {
return _window->isActiveWindow()
&& _window->isVisible()
&& !(_window->windowState() & Qt::WindowMinimized);
return window()->isActiveWindow()
&& window()->isVisible()
&& !(window()->windowState() & Qt::WindowMinimized);
}
void Panel::showAndActivate() {
if (_window->isHidden()) {
_window->show();
if (window()->isHidden()) {
window()->show();
}
const auto state = _window->windowState();
const auto state = window()->windowState();
if (state & Qt::WindowMinimized) {
_window->setWindowState(state & ~Qt::WindowMinimized);
window()->setWindowState(state & ~Qt::WindowMinimized);
}
_window->raise();
_window->activateWindow();
_window->setFocus();
window()->raise();
window()->activateWindow();
window()->setFocus();
}
void Panel::minimize() {
_window->setWindowState(_window->windowState() | Qt::WindowMinimized);
window()->setWindowState(window()->windowState() | Qt::WindowMinimized);
}
void Panel::replaceCall(not_null<Call*> call) {
@@ -134,26 +114,26 @@ void Panel::replaceCall(not_null<Call*> call) {
}
void Panel::initWindow() {
_window->setAttribute(Qt::WA_OpaquePaintEvent);
_window->setAttribute(Qt::WA_NoSystemBackground);
_window->setWindowIcon(
window()->setAttribute(Qt::WA_OpaquePaintEvent);
window()->setAttribute(Qt::WA_NoSystemBackground);
window()->setWindowIcon(
QIcon(QPixmap::fromImage(Image::Empty()->original(), Qt::ColorOnly)));
_window->setTitle(u" "_q);
_window->setTitleStyle(st::callTitle);
window()->setTitle(u" "_q);
window()->setTitleStyle(st::callTitle);
_window->events(
window()->events(
) | rpl::start_with_next([=](not_null<QEvent*> e) {
if (e->type() == QEvent::Close) {
handleClose();
} else if (e->type() == QEvent::KeyPress) {
if ((static_cast<QKeyEvent*>(e.get())->key() == Qt::Key_Escape)
&& _window->isFullScreen()) {
_window->showNormal();
&& window()->isFullScreen()) {
window()->showNormal();
}
}
}, _window->lifetime());
}, window()->lifetime());
_window->setBodyTitleArea([=](QPoint widgetPoint) {
window()->setBodyTitleArea([=](QPoint widgetPoint) {
using Flag = Ui::WindowTitleHitTestFlag;
if (!widget()->rect().contains(widgetPoint)) {
return Flag::None | Flag(0);
@@ -179,28 +159,31 @@ void Panel::initWindow() {
: (Flag::Move | Flag::FullScreen);
});
#ifdef Q_OS_WIN
// On Windows we replace snap-to-top maximizing with fullscreen.
//
// We have to switch first to showNormal, so that showFullScreen
// will remember correct normal window geometry and next showNormal
// will show it instead of a moving maximized window.
//
// We have to do it in InvokeQueued, otherwise it still captures
// the maximized window geometry and saves it.
//
// I couldn't find a less glitchy way to do that *sigh*.
const auto object = _window->windowHandle();
const auto signal = &QWindow::windowStateChanged;
QObject::connect(object, signal, [=](Qt::WindowState state) {
if (state == Qt::WindowMaximized) {
InvokeQueued(object, [=] {
_window->showNormal();
_window->showFullScreen();
});
}
});
#endif // Q_OS_WIN
// Don't do that, it looks awful :(
//#ifdef Q_OS_WIN
// // On Windows we replace snap-to-top maximizing with fullscreen.
// //
// // We have to switch first to showNormal, so that showFullScreen
// // will remember correct normal window geometry and next showNormal
// // will show it instead of a moving maximized window.
// //
// // We have to do it in InvokeQueued, otherwise it still captures
// // the maximized window geometry and saves it.
// //
// // I couldn't find a less glitchy way to do that *sigh*.
// const auto object = window()->windowHandle();
// const auto signal = &QWindow::windowStateChanged;
// QObject::connect(object, signal, [=](Qt::WindowState state) {
// if (state == Qt::WindowMaximized) {
// InvokeQueued(object, [=] {
// window()->showNormal();
// InvokeQueued(object, [=] {
// window()->showFullScreen();
// });
// });
// }
// });
//#endif // Q_OS_WIN
}
void Panel::initWidget() {
@@ -339,7 +322,7 @@ void Panel::reinitWithCall(Call *call) {
_incoming = std::make_unique<Incoming>(
widget(),
_call->videoIncoming(),
_backend);
_window.backend());
_incoming->widget()->hide();
_call->mutedValue(
@@ -519,16 +502,20 @@ void Panel::showControls() {
}
void Panel::closeBeforeDestroy() {
_window->close();
window()->close();
reinitWithCall(nullptr);
}
rpl::lifetime &Panel::lifetime() {
return window()->lifetime();
}
void Panel::initGeometry() {
const auto center = Core::App().getPointForCallPanelCenter();
const auto initRect = QRect(0, 0, st::callWidth, st::callHeight);
_window->setGeometry(initRect.translated(center - initRect.center()));
_window->setMinimumSize({ st::callWidthMin, st::callHeightMin });
_window->show();
window()->setGeometry(initRect.translated(center - initRect.center()));
window()->setMinimumSize({ st::callWidthMin, st::callHeightMin });
window()->show();
updateControlsGeometry();
}
@@ -546,9 +533,9 @@ void Panel::refreshOutgoingPreviewInBody(State state) {
void Panel::toggleFullScreen(bool fullscreen) {
if (fullscreen) {
_window->showFullScreen();
window()->showFullScreen();
} else {
_window->showNormal();
window()->showNormal();
}
}
@@ -724,8 +711,12 @@ void Panel::handleClose() {
}
}
not_null<Ui::Window*> Panel::window() const {
return _window.window();
}
not_null<Ui::RpWidget*> Panel::widget() const {
return _window->body();
return _window.widget();
}
void Panel::stateChanged(State state) {
@@ -741,7 +732,7 @@ void Panel::stateChanged(State state) {
auto toggleButton = [&](auto &&button, bool visible) {
button->toggle(
visible,
_window->isHidden()
window()->isHidden()
? anim::type::instant
: anim::type::normal);
};

View File

@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/object_ptr.h"
#include "calls/calls_call.h"
#include "ui/effects/animations.h"
#include "ui/gl/gl_window.h"
#include "ui/rp_widget.h"
class Image;
@@ -60,6 +61,8 @@ public:
void replaceCall(not_null<Call*> call);
void closeBeforeDestroy();
rpl::lifetime &lifetime();
private:
class Incoming;
using State = Call::State;
@@ -70,7 +73,7 @@ private:
Redial,
};
std::unique_ptr<Ui::Window> createWindow();
[[nodiscard]] not_null<Ui::Window*> window() const;
[[nodiscard]] not_null<Ui::RpWidget*> widget() const;
void paint(QRect clip);
@@ -106,8 +109,7 @@ private:
Call *_call = nullptr;
not_null<UserData*> _user;
Ui::GL::Backend _backend = Ui::GL::Backend();
const std::unique_ptr<Ui::Window> _window;
Ui::GL::Window _window;
std::unique_ptr<Incoming> _incoming;
#ifndef Q_OS_MAC

View File

@@ -223,6 +223,7 @@ void Panel::Incoming::RendererGL::paint(
Assert(data.format == Webrtc::FrameFormat::YUV420);
Assert(!data.yuv420->size.isEmpty());
const auto yuv = data.yuv420;
const auto format = Ui::GL::CurrentSingleComponentFormat();
f.glActiveTexture(GL_TEXTURE0);
_textures.bind(f, 1);
@@ -230,8 +231,8 @@ void Panel::Incoming::RendererGL::paint(
f.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
uploadTexture(
f,
GL_RED,
GL_RED,
format,
format,
yuv->size,
_lumaSize,
yuv->y.stride,
@@ -243,8 +244,8 @@ void Panel::Incoming::RendererGL::paint(
if (upload) {
uploadTexture(
f,
GL_RED,
GL_RED,
format,
format,
yuv->chromaSize,
_chromaSize,
yuv->u.stride,
@@ -255,8 +256,8 @@ void Panel::Incoming::RendererGL::paint(
if (upload) {
uploadTexture(
f,
GL_RED,
GL_RED,
format,
format,
yuv->chromaSize,
_chromaSize,
yuv->v.stride,

View File

@@ -49,6 +49,8 @@ constexpr auto kUpdateSendActionEach = crl::time(500);
constexpr auto kPlayConnectingEach = crl::time(1056) + 2 * crl::time(1000);
constexpr auto kFixManualLargeVideoDuration = 5 * crl::time(1000);
constexpr auto kFixSpeakingLargeVideoDuration = 3 * crl::time(1000);
constexpr auto kFullAsMediumsCount = 4; // 1 Full is like 4 Mediums.
constexpr auto kMaxMediumQualities = 16; // 4 Fulls or 16 Mediums.
[[nodiscard]] std::unique_ptr<Webrtc::MediaDevices> CreateMediaDevices() {
const auto &settings = Core::App().settings();
@@ -187,6 +189,28 @@ struct GroupCall::SinkPointer {
std::weak_ptr<Webrtc::SinkInterface> data;
};
struct GroupCall::VideoTrack {
VideoTrack(bool paused, bool requireARGB32, not_null<PeerData*> peer);
Webrtc::VideoTrack track;
rpl::variable<QSize> trackSize;
not_null<PeerData*> peer;
rpl::lifetime lifetime;
Group::VideoQuality quality = Group::VideoQuality();
bool shown = false;
};
GroupCall::VideoTrack::VideoTrack(
bool paused,
bool requireARGB32,
not_null<PeerData*> peer)
: track((paused
? Webrtc::VideoState::Paused
: Webrtc::VideoState::Active),
requireARGB32)
, peer(peer) {
}
[[nodiscard]] bool IsGroupCallAdmin(
not_null<PeerData*> peer,
not_null<PeerData*> participantPeer) {
@@ -451,6 +475,21 @@ void GroupCall::MediaChannelDescriptionsTask::cancel() {
}
}
not_null<PeerData*> GroupCall::TrackPeer(
const std::unique_ptr<VideoTrack> &track) {
return track->peer;
}
not_null<Webrtc::VideoTrack*> GroupCall::TrackPointer(
const std::unique_ptr<VideoTrack> &track) {
return &track->track;
}
rpl::producer<QSize> GroupCall::TrackSizeValue(
const std::unique_ptr<VideoTrack> &track) {
return track->trackSize.value();
}
GroupCall::GroupCall(
not_null<Delegate*> delegate,
Group::JoinInfo info,
@@ -1064,43 +1103,39 @@ void GroupCall::markEndpointActive(
if (active) {
const auto i = _activeVideoTracks.emplace(
endpoint,
VideoTrack{
.track = std::make_unique<Webrtc::VideoTrack>(
(paused
? Webrtc::VideoState::Paused
: Webrtc::VideoState::Active),
_requireARGB32),
.peer = endpoint.peer,
}).first;
const auto track = i->second.track.get();
std::make_unique<VideoTrack>(
paused,
_requireARGB32,
endpoint.peer)).first;
const auto track = &i->second->track;
track->renderNextFrame(
) | rpl::start_with_next([=] {
auto &activeTrack = _activeVideoTracks[endpoint];
const auto activeTrack = _activeVideoTracks[endpoint].get();
const auto size = track->frameSize();
if (size.isEmpty()) {
track->markFrameShown();
} else if (!activeTrack.shown) {
activeTrack.shown = true;
} else if (!activeTrack->shown) {
activeTrack->shown = true;
markTrackShown(endpoint, true);
}
activeTrack.trackSize = size;
}, i->second.lifetime);
activeTrack->trackSize = size;
}, i->second->lifetime);
const auto size = track->frameSize();
i->second.trackSize = size;
i->second->trackSize = size;
if (!size.isEmpty() || paused) {
i->second.shown = true;
i->second->shown = true;
shown = true;
} else {
track->stateValue(
) | rpl::filter([=](Webrtc::VideoState state) {
return (state == Webrtc::VideoState::Paused)
&& !_activeVideoTracks[endpoint].shown;
&& !_activeVideoTracks[endpoint]->shown;
}) | rpl::start_with_next([=] {
_activeVideoTracks[endpoint].shown = true;
_activeVideoTracks[endpoint]->shown = true;
markTrackShown(endpoint, true);
}, i->second.lifetime);
}, i->second->lifetime);
}
addVideoOutput(i->first.id, { track->sink() });
} else {
@@ -1144,7 +1179,7 @@ void GroupCall::markTrackPaused(const VideoEndpoint &endpoint, bool paused) {
const auto i = _activeVideoTracks.find(endpoint);
Assert(i != end(_activeVideoTracks));
i->second.track->setState(paused
i->second->track.setState(paused
? Webrtc::VideoState::Paused
: Webrtc::VideoState::Active);
}
@@ -1954,6 +1989,10 @@ bool GroupCall::emitShareCameraError() {
void GroupCall::emitShareCameraError(Error error) {
_cameraState = Webrtc::VideoState::Inactive;
if (error == Error::CameraFailed
&& Webrtc::GetVideoInputList().empty()) {
error = Error::NoCamera;
}
_errors.fire_copy(error);
}
@@ -2007,8 +2046,14 @@ void GroupCall::setupOutgoingVideo() {
_cameraCapture = _delegate->groupCallGetVideoCapture(
_cameraInputId);
if (!_cameraCapture) {
return emitShareCameraError(Error::NoCamera);
return emitShareCameraError(Error::CameraFailed);
}
const auto weak = base::make_weak(this);
_cameraCapture->setOnFatalError([=] {
crl::on_main(weak, [=] {
emitShareCameraError(Error::CameraFailed);
});
});
} else {
_cameraCapture->switchToDevice(_cameraInputId.toStdString());
}
@@ -2402,6 +2447,9 @@ void GroupCall::updateRequestedVideoChannels() {
channels.reserve(_activeVideoTracks.size());
const auto &camera = cameraSharingEndpoint();
const auto &screen = screenSharingEndpoint();
auto mediums = 0;
auto fullcameras = 0;
auto fullscreencasts = 0;
for (const auto &[endpoint, video] : _activeVideoTracks) {
const auto &endpointId = endpoint.id;
if (endpointId == camera || endpointId == screen) {
@@ -2414,24 +2462,74 @@ void GroupCall::updateRequestedVideoChannels() {
if (!params) {
continue;
}
const auto min = (video->quality == Group::VideoQuality::Full
&& endpoint.type == VideoEndpointType::Screen)
? Quality::Full
: Quality::Thumbnail;
const auto max = (video->quality == Group::VideoQuality::Full)
? Quality::Full
: (video->quality == Group::VideoQuality::Medium
&& endpoint.type != VideoEndpointType::Screen)
? Quality::Medium
: Quality::Thumbnail;
if (max == Quality::Full) {
if (endpoint.type == VideoEndpointType::Screen) {
++fullscreencasts;
} else {
++fullcameras;
}
} else if (max == Quality::Medium) {
++mediums;
}
channels.push_back({
.audioSsrc = participant->ssrc,
.endpointId = endpointId,
.ssrcGroups = (params->camera.endpointId == endpointId
? params->camera.ssrcGroups
: params->screen.ssrcGroups),
.minQuality = ((video.quality == Group::VideoQuality::Full
&& endpoint.type == VideoEndpointType::Screen)
? Quality::Full
: Quality::Thumbnail),
.maxQuality = ((video.quality == Group::VideoQuality::Full)
? Quality::Full
: (video.quality == Group::VideoQuality::Medium
&& endpoint.type != VideoEndpointType::Screen)
? Quality::Medium
: Quality::Thumbnail),
.minQuality = min,
.maxQuality = max,
});
}
// We limit `count(Full) * kFullAsMediumsCount + count(medium)`.
//
// Try to preserve all qualities; If not
// Try to preserve all screencasts as Full and cameras as Medium; If not
// Try to preserve all screencasts as Full; If not
// Try to preserve all cameras as Medium;
const auto mediumsCount = mediums
+ (fullcameras + fullscreencasts) * kFullAsMediumsCount;
const auto downgradeSome = (mediumsCount > kMaxMediumQualities);
const auto downgradeAll = (fullscreencasts * kFullAsMediumsCount)
> kMaxMediumQualities;
if (downgradeSome) {
for (auto &channel : channels) {
if (channel.maxQuality == Quality::Full) {
const auto camera = (channel.minQuality != Quality::Full);
if (camera) {
channel.maxQuality = Quality::Medium;
} else if (downgradeAll) {
channel.maxQuality
= channel.minQuality
= Quality::Thumbnail;
--fullscreencasts;
}
}
}
mediums += fullcameras;
fullcameras = 0;
if (downgradeAll) {
fullscreencasts = 0;
}
}
if (mediums > kMaxMediumQualities) {
for (auto &channel : channels) {
if (channel.maxQuality == Quality::Medium) {
channel.maxQuality = Quality::Thumbnail;
}
}
}
_instance->setRequestedVideoChannels(std::move(channels));
}
@@ -2911,10 +3009,10 @@ void GroupCall::requestVideoQuality(
return;
}
const auto i = _activeVideoTracks.find(endpoint);
if (i == end(_activeVideoTracks) || i->second.quality == quality) {
if (i == end(_activeVideoTracks) || i->second->quality == quality) {
return;
}
i->second.quality = quality;
i->second->quality = quality;
updateRequestedVideoChannelsDelayed();
}

View File

@@ -98,6 +98,8 @@ struct VideoEndpoint {
std::string id;
[[nodiscard]] bool empty() const noexcept {
Expects(id.empty() || peer != nullptr);
return id.empty();
}
[[nodiscard]] explicit operator bool() const noexcept {
@@ -194,6 +196,15 @@ public:
using GlobalShortcutManager = base::GlobalShortcutManager;
struct VideoTrack;
[[nodiscard]] static not_null<PeerData*> TrackPeer(
const std::unique_ptr<VideoTrack> &track);
[[nodiscard]] static not_null<Webrtc::VideoTrack*> TrackPointer(
const std::unique_ptr<VideoTrack> &track);
[[nodiscard]] static rpl::producer<QSize> TrackSizeValue(
const std::unique_ptr<VideoTrack> &track);
GroupCall(
not_null<Delegate*> delegate,
Group::JoinInfo info,
@@ -321,27 +332,8 @@ public:
-> rpl::producer<VideoEndpoint> {
return _videoEndpointLarge.value();
}
struct VideoTrack {
std::unique_ptr<Webrtc::VideoTrack> track;
rpl::variable<QSize> trackSize;
PeerData *peer = nullptr;
rpl::lifetime lifetime;
Group::VideoQuality quality = Group::VideoQuality();
bool shown = false;
[[nodiscard]] explicit operator bool() const {
return (track != nullptr);
}
[[nodiscard]] bool operator==(const VideoTrack &other) const {
return (track == other.track) && (peer == other.peer);
}
[[nodiscard]] bool operator!=(const VideoTrack &other) const {
return !(*this == other);
}
};
[[nodiscard]] auto activeVideoTracks() const
-> const base::flat_map<VideoEndpoint, VideoTrack> & {
-> const base::flat_map<VideoEndpoint, std::unique_ptr<VideoTrack>> & {
return _activeVideoTracks;
}
[[nodiscard]] auto shownVideoTracks() const
@@ -625,7 +617,9 @@ private:
rpl::event_stream<VideoStateToggle> _videoStreamActiveUpdates;
rpl::event_stream<VideoStateToggle> _videoStreamPausedUpdates;
rpl::event_stream<VideoStateToggle> _videoStreamShownUpdates;
base::flat_map<VideoEndpoint, VideoTrack> _activeVideoTracks;
base::flat_map<
VideoEndpoint,
std::unique_ptr<VideoTrack>> _activeVideoTracks;
base::flat_set<VideoEndpoint> _shownVideoTracks;
rpl::variable<VideoEndpoint> _videoEndpointLarge;
rpl::variable<bool> _videoEndpointPinned = false;

View File

@@ -61,6 +61,7 @@ enum class VideoQuality {
enum class Error {
NoCamera,
CameraFailed,
ScreenFailed,
MutedNoCamera,
MutedNoScreen,
@@ -68,4 +69,13 @@ enum class Error {
DisabledNoScreen,
};
enum class StickedTooltip {
Camera = 0x01,
Microphone = 0x02,
};
constexpr inline bool is_flag_type(StickedTooltip) {
return true;
}
using StickedTooltips = base::flags<StickedTooltip>;
} // namespace Calls::Group

View File

@@ -34,7 +34,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/edit_participants_box.h" // SubscribeToMigration.
#include "window/window_controller.h" // Controller::sessionController.
#include "window/window_session_controller.h"
#include "media/view/media_view_pip.h"
#include "webrtc/webrtc_video_track.h"
#include "styles/style_calls.h"
@@ -1236,12 +1235,10 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
not_null<PeerListRow*> row) {
const auto participantPeer = row->peer();
const auto real = static_cast<Row*>(row.get());
auto result = base::make_unique_q<Ui::PopupMenu>(
parent,
st::groupCallPopupMenu);
const auto muteState = real->state();
const auto muted = (muteState == Row::State::Muted)
|| (muteState == Row::State::RaisedHand);
const auto addVolumeItem = !muted || isMe(participantPeer);
const auto admin = IsGroupCallAdmin(_peer, participantPeer);
const auto session = &_peer->session();
const auto getCurrentWindow = [=]() -> Window::SessionController* {
@@ -1262,6 +1259,12 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
}
return getCurrentWindow();
};
auto result = base::make_unique_q<Ui::PopupMenu>(
parent,
(addVolumeItem
? st::groupCallPopupMenuWithVolume
: st::groupCallPopupMenu));
const auto weakMenu = Ui::MakeWeak(result.get());
const auto performOnMainWindow = [=](auto callback) {
if (const auto window = getWindow()) {
@@ -1442,7 +1445,8 @@ void Members::Controller::addMuteActionsToContextMenu(
auto mutesFromVolume = rpl::never<bool>() | rpl::type_erased();
if (!muted || _call->joinAs() == participantPeer) {
const auto addVolumeItem = !muted || isMe(participantPeer);
if (addVolumeItem) {
auto otherParticipantStateValue
= _call->otherParticipantStateValue(
) | rpl::filter([=](const Group::ParticipantState &data) {
@@ -1451,7 +1455,7 @@ void Members::Controller::addMuteActionsToContextMenu(
auto volumeItem = base::make_unique_q<MenuVolumeItem>(
menu->menu(),
st::groupCallPopupMenu.menu,
st::groupCallPopupVolumeMenu,
otherParticipantStateValue,
row->volume(),
Group::kMaxVolume,
@@ -1490,7 +1494,15 @@ void Members::Controller::addMuteActionsToContextMenu(
}
}, volumeItem->lifetime());
if (!menu->empty()) {
menu->addSeparator();
}
menu->addAction(std::move(volumeItem));
if (!isMe(participantPeer)) {
menu->addSeparator();
}
};
const auto muteAction = [&]() -> QAction* {

View File

@@ -48,11 +48,13 @@ void EditGroupCallTitleBox(
box->setFocusCallback([=] {
input->setFocusFast();
});
box->addButton(tr::lng_settings_save(), [=] {
const auto submit = [=] {
const auto result = input->getLastText().trimmed();
box->closeBox();
done(result);
});
};
QObject::connect(input, &Ui::InputField::submitted, submit);
box->addButton(tr::lng_settings_save(), submit);
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}
@@ -76,11 +78,13 @@ void StartGroupCallRecordingBox(
box->setFocusCallback([=] {
input->setFocusFast();
});
box->addButton(tr::lng_group_call_recording_start_button(), [=] {
const auto submit = [=] {
const auto result = input->getLastText().trimmed();
box->closeBox();
done(result);
});
};
QObject::connect(input, &Ui::InputField::submitted, submit);
box->addButton(tr::lng_group_call_recording_start_button(), submit);
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}

View File

@@ -20,13 +20,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/platform/ui_platform_utility.h"
#include "ui/controls/call_mute_button.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/window.h"
#include "ui/widgets/call_button.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/dropdown_menu.h"
#include "ui/widgets/input_fields.h"
#include "ui/widgets/tooltip.h"
#include "ui/gl/gl_detection.h"
#include "ui/widgets/window.h"
#include "ui/chat/group_call_bar.h"
#include "ui/layers/layer_manager.h"
#include "ui/layers/generic_box.h"
@@ -47,13 +46,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "base/event_filter.h"
#include "base/unixtime.h"
#include "base/platform/base_platform_info.h"
#include "base/qt_signal_producer.h"
#include "base/timer_rpl.h"
#include "app.h"
#include "apiwrap.h" // api().kickParticipant.
#include "webrtc/webrtc_video_track.h"
#include "webrtc/webrtc_media_devices.h" // UniqueDesktopCaptureSource.
#include "webrtc/webrtc_audio_input_tester.h"
#include "styles/style_calls.h"
#include "styles/style_layers.h"
@@ -71,6 +70,10 @@ constexpr auto kRecordingOpacity = 0.6;
constexpr auto kStartNoConfirmation = TimeId(10);
constexpr auto kControlsBackgroundOpacity = 0.8;
constexpr auto kOverrideActiveColorBgAlpha = 172;
constexpr auto kMicrophoneTooltipAfterLoudCount = 3;
constexpr auto kDropLoudAfterQuietCount = 5;
constexpr auto kMicrophoneTooltipLevelThreshold = 0.2;
constexpr auto kMicrophoneTooltipCheckInterval = crl::time(500);
} // namespace
@@ -84,17 +87,60 @@ struct Panel::ControlsBackgroundNarrow {
Ui::RpWidget blocker;
};
class Panel::MicLevelTester final {
public:
explicit MicLevelTester(Fn<void()> show);
[[nodiscard]] bool showTooltip() const;
private:
void check();
Fn<void()> _show;
base::Timer _timer;
Webrtc::AudioInputTester _tester;
int _loudCount = 0;
int _quietCount = 0;
};
Panel::MicLevelTester::MicLevelTester(Fn<void()> show)
: _show(std::move(show))
, _timer([=] { check(); })
, _tester(
Core::App().settings().callAudioBackend(),
Core::App().settings().callInputDeviceId()) {
_timer.callEach(kMicrophoneTooltipCheckInterval);
}
bool Panel::MicLevelTester::showTooltip() const {
return (_loudCount >= kMicrophoneTooltipAfterLoudCount);
}
void Panel::MicLevelTester::check() {
const auto level = _tester.getAndResetLevel();
if (level >= kMicrophoneTooltipLevelThreshold) {
_quietCount = 0;
if (++_loudCount >= kMicrophoneTooltipAfterLoudCount) {
_show();
}
} else if (_loudCount > 0 && ++_quietCount >= kDropLoudAfterQuietCount) {
_quietCount = 0;
_loudCount = 0;
}
}
Panel::Panel(not_null<GroupCall*> call)
: _call(call)
, _peer(call->peer())
, _window(createWindow())
, _layerBg(std::make_unique<Ui::LayerManager>(_window->body()))
, _layerBg(std::make_unique<Ui::LayerManager>(widget()))
#ifndef Q_OS_MAC
, _controls(std::make_unique<Ui::Platform::TitleControls>(
_window->body(),
widget(),
st::groupCallTitle))
#endif // !Q_OS_MAC
, _viewport(std::make_unique<Viewport>(widget(), PanelMode::Wide, _backend))
, _viewport(
std::make_unique<Viewport>(widget(), PanelMode::Wide, _window.backend()))
, _mute(std::make_unique<Ui::CallMuteButton>(
widget(),
st::callMuteButton,
@@ -112,6 +158,8 @@ Panel::Panel(not_null<GroupCall*> call)
: Ui::CallMuteButtonType::ScheduledSilent),
}))
, _hangup(widget(), st::groupCallHangup)
, _stickedTooltipsShown(Core::App().settings().hiddenGroupCallTooltips()
& ~StickedTooltip::Microphone) // Always show tooltip about mic.
, _toasts(std::make_unique<Toasts>(this)) {
_layerBg->setStyleOverrides(&st::groupCallBox, &st::groupCallLayerBox);
_layerBg->setHideByBackgroundClick(true);
@@ -123,7 +171,7 @@ Panel::Panel(not_null<GroupCall*> call)
SubscribeToMigration(
_peer,
_window->lifetime(),
lifetime(),
[=](not_null<ChannelData*> channel) { migrate(channel); });
setupRealCallViewers();
@@ -139,30 +187,11 @@ Panel::~Panel() {
_viewport = nullptr;
}
std::unique_ptr<Ui::Window> Panel::createWindow() {
auto result = std::make_unique<Ui::Window>();
const auto capabilities = Ui::GL::CheckCapabilities(result.get());
const auto use = Platform::IsMac()
? true
: Platform::IsWindows()
? capabilities.supported
: capabilities.transparency;
LOG(("OpenGL: %1 (Calls::Group::Viewport)").arg(Logs::b(use)));
_backend = use ? Ui::GL::Backend::OpenGL : Ui::GL::Backend::Raster;
if (use) {
return result;
}
// We have to create a new window, if OpenGL initialization failed.
return std::make_unique<Ui::Window>();
}
void Panel::setupRealCallViewers() {
_call->real(
) | rpl::start_with_next([=](not_null<Data::GroupCall*> real) {
subscribeToChanges(real);
}, _window->lifetime());
}, lifetime());
}
not_null<GroupCall*> Panel::call() const {
@@ -170,9 +199,9 @@ not_null<GroupCall*> Panel::call() const {
}
bool Panel::isActive() const {
return _window->isActiveWindow()
&& _window->isVisible()
&& !(_window->windowState() & Qt::WindowMinimized);
return window()->isActiveWindow()
&& window()->isVisible()
&& !(window()->windowState() & Qt::WindowMinimized);
}
void Panel::showToast(TextWithEntities &&text, crl::time duration) {
@@ -187,24 +216,24 @@ void Panel::showToast(TextWithEntities &&text, crl::time duration) {
}
void Panel::minimize() {
_window->setWindowState(_window->windowState() | Qt::WindowMinimized);
window()->setWindowState(window()->windowState() | Qt::WindowMinimized);
}
void Panel::close() {
_window->close();
window()->close();
}
void Panel::showAndActivate() {
if (_window->isHidden()) {
_window->show();
if (window()->isHidden()) {
window()->show();
}
const auto state = _window->windowState();
const auto state = window()->windowState();
if (state & Qt::WindowMinimized) {
_window->setWindowState(state & ~Qt::WindowMinimized);
window()->setWindowState(state & ~Qt::WindowMinimized);
}
_window->raise();
_window->activateWindow();
_window->setFocus();
window()->raise();
window()->activateWindow();
window()->setFocus();
}
void Panel::migrate(not_null<ChannelData*> channel) {
@@ -219,12 +248,12 @@ void Panel::subscribeToPeerChanges() {
Info::Profile::NameValue(
_peer
) | rpl::start_with_next([=](const TextWithEntities &name) {
_window->setTitle(name.text);
window()->setTitle(name.text);
}, _peerLifetime);
}
QWidget *Panel::chooseSourceParent() {
return _window.get();
return window().get();
}
QString Panel::chooseSourceActiveDeviceId() {
@@ -232,7 +261,7 @@ QString Panel::chooseSourceActiveDeviceId() {
}
rpl::lifetime &Panel::chooseSourceInstanceLifetime() {
return _window->lifetime();
return lifetime();
}
void Panel::chooseSourceAccepted(const QString &deviceId) {
@@ -244,15 +273,15 @@ void Panel::chooseSourceStop() {
}
void Panel::initWindow() {
_window->setAttribute(Qt::WA_OpaquePaintEvent);
_window->setAttribute(Qt::WA_NoSystemBackground);
_window->setWindowIcon(
window()->setAttribute(Qt::WA_OpaquePaintEvent);
window()->setAttribute(Qt::WA_NoSystemBackground);
window()->setWindowIcon(
QIcon(QPixmap::fromImage(Image::Empty()->original(), Qt::ColorOnly)));
_window->setTitleStyle(st::groupCallTitle);
window()->setTitleStyle(st::groupCallTitle);
subscribeToPeerChanges();
base::install_event_filter(_window.get(), [=](not_null<QEvent*> e) {
base::install_event_filter(window().get(), [=](not_null<QEvent*> e) {
if (e->type() == QEvent::Close && handleClose()) {
e->ignore();
return base::EventFilterResult::Cancel;
@@ -267,7 +296,7 @@ void Panel::initWindow() {
return base::EventFilterResult::Continue;
});
_window->setBodyTitleArea([=](QPoint widgetPoint) {
window()->setBodyTitleArea([=](QPoint widgetPoint) {
using Flag = Ui::WindowTitleHitTestFlag;
const auto titleRect = QRect(
0,
@@ -286,7 +315,7 @@ void Panel::initWindow() {
_call->hasVideoWithFramesValue(
) | rpl::start_with_next([=] {
updateMode();
}, _window->lifetime());
}, lifetime());
}
void Panel::initWidget() {
@@ -295,7 +324,7 @@ void Panel::initWidget() {
widget()->paintRequest(
) | rpl::start_with_next([=](QRect clip) {
paint(clip);
}, widget()->lifetime());
}, lifetime());
widget()->sizeValue(
) | rpl::skip(1) | rpl::start_with_next([=](QSize size) {
@@ -306,7 +335,7 @@ void Panel::initWidget() {
// title geometry depends on _controls->geometry,
// which is not updated here yet.
crl::on_main(widget(), [=] { refreshTitle(); });
}, widget()->lifetime());
}, lifetime());
}
void Panel::endCall() {
@@ -380,7 +409,7 @@ void Panel::initControls() {
_call->canManageValue()
) | rpl::start_with_next([=] {
refreshTopButton();
}, widget()->lifetime());
}, lifetime());
_hangup->setClickedCallback([=] { endCall(); });
@@ -418,7 +447,9 @@ void Panel::initControls() {
}
_call->stateValue(
) | rpl::filter([](State state) {
) | rpl::before_next([=] {
showStickedTooltip();
}) | rpl::filter([](State state) {
return (state == State::HangingUp)
|| (state == State::Ended)
|| (state == State::FailedHangingUp)
@@ -499,18 +530,22 @@ void Panel::refreshVideoButtons(std::optional<bool> overrideWideMode) {
&st::groupCallVideoActiveSmall);
_video->show();
_video->setClickedCallback([=] {
hideStickedTooltip(
StickedTooltip::Camera,
StickedTooltipHide::Activated);
_call->toggleVideo(!_call->isSharingCamera());
});
_video->setColorOverrides(
toggleableOverrides(_call->isSharingCameraValue()));
_call->isSharingCameraValue(
) | rpl::start_with_next([=](bool sharing) {
if (sharing) {
hideStickedTooltip(
StickedTooltip::Camera,
StickedTooltipHide::Activated);
}
_video->setProgress(sharing ? 1. : 0.);
}, _video->lifetime());
_call->mutedValue(
) | rpl::start_with_next([=] {
updateButtonsGeometry();
}, _video->lifetime());
}
if (!_screenShare) {
_screenShare.create(widget(), st::groupCallScreenShareSmall);
@@ -536,6 +571,46 @@ void Panel::refreshVideoButtons(std::optional<bool> overrideWideMode) {
updateButtonsGeometry();
}
void Panel::hideStickedTooltip(StickedTooltipHide hide) {
if (!_stickedTooltipClose || !_niceTooltipControl) {
return;
}
if (_niceTooltipControl.data() == _video.data()) {
hideStickedTooltip(StickedTooltip::Camera, hide);
} else if (_niceTooltipControl.data() == _mute->outer().get()) {
hideStickedTooltip(StickedTooltip::Microphone, hide);
}
}
void Panel::hideStickedTooltip(
StickedTooltip type,
StickedTooltipHide hide) {
if (hide != StickedTooltipHide::Unavailable) {
_stickedTooltipsShown |= type;
if (hide == StickedTooltipHide::Discarded) {
Core::App().settings().setHiddenGroupCallTooltip(type);
Core::App().saveSettingsDelayed();
}
}
const auto control = (type == StickedTooltip::Camera)
? _video.data()
: (type == StickedTooltip::Microphone)
? _mute->outer().get()
: nullptr;
if (_niceTooltipControl.data() == control) {
hideNiceTooltip();
}
}
void Panel::hideNiceTooltip() {
if (!_niceTooltip) {
return;
}
_stickedTooltipClose = nullptr;
_niceTooltip.release()->toggleAnimated(false);
_niceTooltipControl = nullptr;
}
void Panel::initShareAction() {
const auto showBoxCallback = [=](object_ptr<Ui::BoxContent> next) {
_layerBg->showBox(std::move(next));
@@ -552,7 +627,7 @@ void Panel::initShareAction() {
callback();
}
};
widget()->lifetime().add(std::move(shareLinkLifetime));
lifetime().add(std::move(shareLinkLifetime));
}
void Panel::setupRealMuteButtonState(not_null<Data::GroupCall*> real) {
@@ -628,7 +703,7 @@ void Panel::setupScheduledLabels(rpl::producer<TimeId> date) {
) | rpl::map([=](TimeId date) {
_countdownData = std::make_shared<Ui::GroupCallScheduledLeft>(date);
return rpl::empty_value();
}) | rpl::start_spawning(widget()->lifetime());
}) | rpl::start_spawning(lifetime());
_countdown = Ui::CreateGradientLabel(widget(), rpl::duplicate(
countdownCreated
@@ -696,7 +771,7 @@ void Panel::setupMembers() {
_countdown.destroy();
_startsWhen.destroy();
_members.create(widget(), _call, mode(), _backend);
_members.create(widget(), _call, mode(), _window.backend());
setupVideo(_viewport.get());
setupVideo(_members->viewport());
@@ -753,40 +828,40 @@ void Panel::setupMembers() {
}
void Panel::enlargeVideo() {
_lastSmallGeometry = _window->geometry();
_lastSmallGeometry = window()->geometry();
const auto available = _window->screen()->availableGeometry();
const auto available = window()->screen()->availableGeometry();
const auto width = std::max(
_window->width(),
window()->width(),
std::max(
std::min(available.width(), st::groupCallWideModeSize.width()),
st::groupCallWideModeWidthMin));
const auto height = std::max(
_window->height(),
window()->height(),
std::min(available.height(), st::groupCallWideModeSize.height()));
auto geometry = QRect(_window->pos(), QSize(width, height));
auto geometry = QRect(window()->pos(), QSize(width, height));
if (geometry.x() < available.x()) {
geometry.setX(std::min(available.x(), _window->x()));
geometry.moveLeft(std::min(available.x(), window()->x()));
}
if (geometry.x() + geometry.width()
> available.x() + available.width()) {
geometry.setX(std::max(
geometry.moveLeft(std::max(
available.x() + available.width(),
_window->x() + _window->width()) - geometry.width());
window()->x() + window()->width()) - geometry.width());
}
if (geometry.y() < available.y()) {
geometry.setY(std::min(available.y(), _window->y()));
geometry.moveTop(std::min(available.y(), window()->y()));
}
if (geometry.y() + geometry.height() > available.y() + available.height()) {
geometry.setY(std::max(
geometry.moveTop(std::max(
available.y() + available.height(),
_window->y() + _window->height()) - geometry.height());
window()->y() + window()->height()) - geometry.height());
}
if (_lastLargeMaximized) {
_window->setWindowState(
_window->windowState() | Qt::WindowMaximized);
window()->setWindowState(
window()->windowState() | Qt::WindowMaximized);
} else {
_window->setGeometry((_lastLargeGeometry
window()->setGeometry((_lastLargeGeometry
&& available.intersects(*_lastLargeGeometry))
? *_lastLargeGeometry
: geometry);
@@ -794,23 +869,23 @@ void Panel::enlargeVideo() {
}
void Panel::minimizeVideo() {
if (_window->windowState() & Qt::WindowMaximized) {
if (window()->windowState() & Qt::WindowMaximized) {
_lastLargeMaximized = true;
_window->setWindowState(
_window->windowState() & ~Qt::WindowMaximized);
window()->setWindowState(
window()->windowState() & ~Qt::WindowMaximized);
} else {
_lastLargeMaximized = false;
_lastLargeGeometry = _window->geometry();
_lastLargeGeometry = window()->geometry();
}
const auto available = _window->screen()->availableGeometry();
const auto available = window()->screen()->availableGeometry();
const auto width = st::groupCallWidth;
const auto height = st::groupCallHeight;
auto geometry = QRect(
_window->x() + (_window->width() - width) / 2,
_window->y() + (_window->height() - height) / 2,
window()->x() + (window()->width() - width) / 2,
window()->y() + (window()->height() - height) / 2,
width,
height);
_window->setGeometry((_lastSmallGeometry
window()->setGeometry((_lastSmallGeometry
&& available.intersects(*_lastSmallGeometry))
? *_lastSmallGeometry
: geometry);
@@ -838,14 +913,17 @@ void Panel::raiseControls() {
}
}
_mute->raise();
if (_niceTooltip) {
_niceTooltip->raise();
}
}
void Panel::setupVideo(not_null<Viewport*> viewport) {
const auto setupTile = [=](
const VideoEndpoint &endpoint,
const GroupCall::VideoTrack &track) {
const std::unique_ptr<GroupCall::VideoTrack> &track) {
using namespace rpl::mappers;
const auto row = _members->lookupRow(track.peer);
const auto row = _members->lookupRow(GroupCall::TrackPeer(track));
Assert(row != nullptr);
auto pinned = rpl::combine(
_call->videoEndpointLargeValue(),
@@ -853,8 +931,8 @@ void Panel::setupVideo(not_null<Viewport*> viewport) {
) | rpl::map(_1 == endpoint && _2);
viewport->add(
endpoint,
VideoTileTrack{ track.track.get(), row },
track.trackSize.value(),
VideoTileTrack{ GroupCall::TrackPointer(track), row },
GroupCall::TrackSizeValue(track),
std::move(pinned));
};
for (const auto &[endpoint, track] : _call->activeVideoTracks()) {
@@ -908,18 +986,24 @@ void Panel::toggleWideControls(bool shown) {
}
_showWideControls = shown;
crl::on_main(widget(), [=] {
if (_wideControlsShown == _showWideControls) {
return;
}
_wideControlsShown = _showWideControls;
_wideControlsAnimation.start(
[=] { updateButtonsGeometry(); },
_wideControlsShown ? 0. : 1.,
_wideControlsShown ? 1. : 0.,
st::slideWrapDuration);
updateWideControlsVisibility();
});
}
void Panel::updateWideControlsVisibility() {
const auto shown = _showWideControls
|| (_stickedTooltipClose != nullptr);
if (_wideControlsShown == shown) {
return;
}
_wideControlsShown = shown;
_wideControlsAnimation.start(
[=] { updateButtonsGeometry(); },
_wideControlsShown ? 0. : 1.,
_wideControlsShown ? 1. : 0.,
st::slideWrapDuration);
}
void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
const auto validateRecordingMark = [=](bool recording) {
if (!recording && _recordingMark) {
@@ -980,7 +1064,7 @@ void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
: tr::lng_group_call_recording_stopped)(
tr::now,
Ui::Text::RichLangValue));
}, widget()->lifetime());
}, lifetime());
validateRecordingMark(real->recordStartDate() != 0);
rpl::combine(
@@ -988,14 +1072,27 @@ void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
_call->isSharingCameraValue()
) | rpl::start_with_next([=] {
refreshVideoButtons();
}, widget()->lifetime());
showStickedTooltip();
}, lifetime());
rpl::combine(
_call->videoIsWorkingValue(),
_call->isSharingScreenValue()
) | rpl::start_with_next([=] {
refreshTopButton();
}, widget()->lifetime());
}, lifetime());
_call->mutedValue(
) | rpl::skip(1) | rpl::start_with_next([=](MuteState state) {
updateButtonsGeometry();
if (state == MuteState::Active
|| state == MuteState::PushToTalk) {
hideStickedTooltip(
StickedTooltip::Microphone,
StickedTooltipHide::Activated);
}
showStickedTooltip();
}, lifetime());
updateControlsGeometry();
}
@@ -1041,7 +1138,7 @@ void Panel::refreshTopButton() {
chooseJoinAs();
});
updateControlsGeometry();
}, widget()->lifetime());
}, lifetime());
} else {
_menuToggle.destroy();
_joinAsToggle.destroy();
@@ -1295,7 +1392,7 @@ void Panel::initLayout() {
) | rpl::start_with_next([=] {
// _menuToggle geometry depends on _controls arrangement.
crl::on_main(widget(), [=] { updateControlsGeometry(); });
}, widget()->lifetime());
}, lifetime());
#endif // !Q_OS_MAC
}
@@ -1307,16 +1404,20 @@ void Panel::showControls() {
}
void Panel::closeBeforeDestroy() {
_window->close();
window()->close();
_callLifetime.destroy();
}
rpl::lifetime &Panel::lifetime() {
return window()->lifetime();
}
void Panel::initGeometry() {
const auto center = Core::App().getPointForCallPanelCenter();
const auto rect = QRect(0, 0, st::groupCallWidth, st::groupCallHeight);
_window->setGeometry(rect.translated(center - rect.center()));
_window->setMinimumSize(rect.size());
_window->show();
window()->setGeometry(rect.translated(center - rect.center()));
window()->setMinimumSize(rect.size());
window()->show();
}
QRect Panel::computeTitleRect() const {
@@ -1352,7 +1453,10 @@ bool Panel::updateMode() {
_call->showVideoEndpointLarge({});
}
refreshVideoButtons(wide);
_niceTooltip.destroy();
if (!_stickedTooltipClose
|| _niceTooltipControl.data() != _mute->outer().get()) {
_niceTooltip.destroy();
}
_mode = mode;
if (_title) {
_title->setTextColorOverride(wide
@@ -1373,6 +1477,7 @@ bool Panel::updateMode() {
updateButtonsStyles();
refreshControlsBackground();
updateControlsGeometry();
showStickedTooltip();
return true;
}
@@ -1557,8 +1662,12 @@ void Panel::trackControl(Ui::RpWidget *widget, rpl::lifetime &lifetime) {
}
void Panel::trackControlOver(not_null<Ui::RpWidget*> control, bool over) {
if (_niceTooltip) {
_niceTooltip.release()->toggleAnimated(false);
if (_stickedTooltipClose) {
if (!over) {
return;
}
} else {
hideNiceTooltip();
}
if (over) {
Ui::Integration::Instance().registerLeaveSubscription(control);
@@ -1569,7 +1678,52 @@ void Panel::trackControlOver(not_null<Ui::RpWidget*> control, bool over) {
toggleWideControls(over);
}
void Panel::showNiceTooltip(not_null<Ui::RpWidget*> control) {
void Panel::showStickedTooltip() {
static const auto kHasCamera = !Webrtc::GetVideoInputList().empty();
const auto callReady = (_call->state() == State::Joined
|| _call->state() == State::Connecting);
if (!(_stickedTooltipsShown & StickedTooltip::Camera)
&& callReady
&& (_mode.current() == PanelMode::Wide)
&& _video
&& _call->videoIsWorking()
&& !_call->mutedByAdmin()
&& kHasCamera) { // Don't recount this every time for now.
showNiceTooltip(_video, NiceTooltipType::Sticked);
return;
}
hideStickedTooltip(
StickedTooltip::Camera,
StickedTooltipHide::Unavailable);
if (!(_stickedTooltipsShown & StickedTooltip::Microphone)
&& callReady
&& _mute
&& !_call->mutedByAdmin()) {
if (_stickedTooltipClose) {
// Showing already.
return;
} else if (!_micLevelTester) {
// Check if there is incoming sound.
_micLevelTester = std::make_unique<MicLevelTester>([=] {
showStickedTooltip();
});
}
if (_micLevelTester->showTooltip()) {
_micLevelTester = nullptr;
showNiceTooltip(_mute->outer(), NiceTooltipType::Sticked);
}
return;
}
_micLevelTester = nullptr;
hideStickedTooltip(
StickedTooltip::Microphone,
StickedTooltipHide::Unavailable);
}
void Panel::showNiceTooltip(
not_null<Ui::RpWidget*> control,
NiceTooltipType type) {
auto text = [&]() -> rpl::producer<QString> {
if (control == _screenShare.data()) {
if (_call->mutedByAdmin()) {
@@ -1595,40 +1749,98 @@ void Panel::showNiceTooltip(not_null<Ui::RpWidget*> control) {
}
return rpl::producer<QString>();
}();
if (!text
|| _wideControlsAnimation.animating()
|| !_wideControlsShown) {
if (!text || _stickedTooltipClose) {
return;
} else if (_wideControlsAnimation.animating() || !_wideControlsShown) {
if (type == NiceTooltipType::Normal) {
return;
}
}
const auto inner = [&]() -> Ui::RpWidget* {
const auto normal = (type == NiceTooltipType::Normal);
auto container = normal
? nullptr
: Ui::CreateChild<Ui::RpWidget>(widget().get());
const auto label = Ui::CreateChild<Ui::FlatLabel>(
(normal ? widget().get() : container),
std::move(text),
st::groupCallNiceTooltipLabel);
if (normal) {
return label;
}
const auto button = Ui::CreateChild<Ui::IconButton>(
container,
st::groupCallStickedTooltipClose);
rpl::combine(
label->sizeValue(),
button->sizeValue()
) | rpl::start_with_next([=](QSize text, QSize close) {
const auto height = std::max(text.height(), close.height());
container->resize(text.width() + close.width(), height);
label->move(0, (height - text.height()) / 2);
button->move(text.width(), (height - close.height()) / 2);
}, container->lifetime());
button->setClickedCallback([=] {
hideStickedTooltip(StickedTooltipHide::Discarded);
});
_stickedTooltipClose = button;
updateWideControlsVisibility();
return container;
}();
_niceTooltip.create(
widget().get(),
object_ptr<Ui::FlatLabel>(
widget().get(),
std::move(text),
st::groupCallNiceTooltipLabel),
st::groupCallNiceTooltip);
object_ptr<Ui::RpWidget>::fromRaw(inner),
(type == NiceTooltipType::Sticked
? st::groupCallStickedTooltip
: st::groupCallNiceTooltip));
const auto tooltip = _niceTooltip.data();
const auto weak = QPointer<QWidget>(tooltip);
const auto destroy = [=] {
delete weak.data();
};
tooltip->setAttribute(Qt::WA_TransparentForMouseEvents);
if (type != NiceTooltipType::Sticked) {
tooltip->setAttribute(Qt::WA_TransparentForMouseEvents);
}
tooltip->setHiddenCallback(destroy);
base::qt_signal_producer(
control.get(),
&QObject::destroyed
) | rpl::start_with_next(destroy, tooltip->lifetime());
const auto geometry = control->geometry();
_niceTooltipControl = control;
updateTooltipGeometry();
tooltip->toggleAnimated(true);
}
void Panel::updateTooltipGeometry() {
if (!_niceTooltip) {
return;
} else if (!_niceTooltipControl) {
hideNiceTooltip();
return;
}
const auto geometry = _niceTooltipControl->geometry();
const auto weak = QPointer<QWidget>(_niceTooltip);
const auto countPosition = [=](QSize size) {
const auto strong = weak.data();
if (!strong) {
return QPoint();
}
const auto wide = (_mode.current() == PanelMode::Wide);
const auto top = geometry.y()
- st::groupCallNiceTooltipTop
- (wide ? st::groupCallNiceTooltipTop : 0)
- size.height();
const auto middle = geometry.center().x();
if (!strong) {
return QPoint();
} else if (!wide) {
return QPoint(
std::max(
std::min(
middle - size.width() / 2,
(widget()->width()
- st::groupCallMembersMargin.right()
- size.width())),
st::groupCallMembersMargin.left()),
top);
}
const auto back = _controlsBackgroundWide.data();
if (size.width() >= _viewport->widget()->width()) {
return QPoint(_viewport->widget()->x(), top);
@@ -1645,8 +1857,7 @@ void Panel::showNiceTooltip(not_null<Ui::RpWidget*> control) {
return QPoint(middle - size.width() / 2, top);
}
};
tooltip->pointAt(geometry, RectPart::Top, countPosition);
tooltip->toggleAnimated(true);
_niceTooltip->pointAt(geometry, RectPart::Top, countPosition);
}
void Panel::trackControls(bool track) {
@@ -1832,6 +2043,7 @@ void Panel::updateButtonsGeometry() {
width,
st::groupCallMembersBottomSkip);
}
updateTooltipGeometry();
}
bool Panel::videoButtonInNarrowMode() const {
@@ -1999,14 +2211,18 @@ void Panel::paint(QRect clip) {
bool Panel::handleClose() {
if (_call) {
_window->hide();
window()->hide();
return true;
}
return false;
}
not_null<Ui::Window*> Panel::window() const {
return _window.window();
}
not_null<Ui::RpWidget*> Panel::widget() const {
return _window->body();
return _window.widget();
}
} // namespace Calls::Group

View File

@@ -11,9 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer.h"
#include "base/object_ptr.h"
#include "calls/group/calls_group_call.h"
#include "calls/group/calls_group_common.h"
#include "calls/group/calls_choose_join_as.h"
#include "calls/group/ui/desktop_capture_choose_source.h"
#include "ui/effects/animations.h"
#include "ui/gl/gl_window.h"
#include "ui/rp_widget.h"
class Image;
@@ -37,14 +39,10 @@ template <typename Widget>
class FadeWrap;
template <typename Widget>
class PaddingWrap;
class Window;
class ScrollArea;
class GenericBox;
class LayerManager;
class GroupCallScheduledLeft;
namespace GL {
enum class Backend;
} // namespace GL
namespace Toast {
class Instance;
} // namespace Toast
@@ -64,6 +62,7 @@ class Toasts;
class Members;
class Viewport;
enum class PanelMode;
enum class StickedTooltip;
class Panel final : private Ui::DesktopCapture::ChooseSourceDelegate {
public:
@@ -80,11 +79,24 @@ public:
void showAndActivate();
void closeBeforeDestroy();
rpl::lifetime &lifetime();
private:
using State = GroupCall::State;
struct ControlsBackgroundNarrow;
std::unique_ptr<Ui::Window> createWindow();
enum class NiceTooltipType {
Normal,
Sticked,
};
enum class StickedTooltipHide {
Unavailable,
Activated,
Discarded,
};
class MicLevelTester;
[[nodiscard]] not_null<Ui::Window*> window() const;
[[nodiscard]] not_null<Ui::RpWidget*> widget() const;
[[nodiscard]] PanelMode mode() const;
@@ -111,11 +123,18 @@ private:
void trackControl(Ui::RpWidget *widget, rpl::lifetime &lifetime);
void trackControlOver(not_null<Ui::RpWidget*> control, bool over);
void showNiceTooltip(not_null<Ui::RpWidget*> control);
void showNiceTooltip(
not_null<Ui::RpWidget*> control,
NiceTooltipType type = NiceTooltipType::Normal);
void showStickedTooltip();
void hideStickedTooltip(StickedTooltipHide hide);
void hideStickedTooltip(StickedTooltip type, StickedTooltipHide hide);
void hideNiceTooltip();
bool updateMode();
void updateControlsGeometry();
void updateButtonsGeometry();
void updateTooltipGeometry();
void updateButtonsStyles();
void updateMembersGeometry();
void refreshControlsBackground();
@@ -127,6 +146,7 @@ private:
std::optional<bool> overrideWideMode = std::nullopt);
void refreshTopButton();
void toggleWideControls(bool shown);
void updateWideControlsVisibility();
[[nodiscard]] bool videoButtonInNarrowMode() const;
void endCall();
@@ -156,8 +176,7 @@ private:
const not_null<GroupCall*> _call;
not_null<PeerData*> _peer;
Ui::GL::Backend _backend = Ui::GL::Backend();
const std::unique_ptr<Ui::Window> _window;
Ui::GL::Window _window;
const std::unique_ptr<Ui::LayerManager> _layerBg;
rpl::variable<PanelMode> _mode;
@@ -202,11 +221,16 @@ private:
std::unique_ptr<Ui::CallMuteButton> _mute;
object_ptr<Ui::CallButton> _hangup;
object_ptr<Ui::ImportantTooltip> _niceTooltip = { nullptr };
QPointer<Ui::IconButton> _stickedTooltipClose;
QPointer<Ui::RpWidget> _niceTooltipControl;
StickedTooltips _stickedTooltipsShown;
Fn<void()> _callShareLinkCallback;
const std::unique_ptr<Toasts> _toasts;
base::weak_ptr<Ui::Toast::Instance> _lastToast;
std::unique_ptr<MicLevelTester> _micLevelTester;
rpl::lifetime _peerLifetime;
};

View File

@@ -493,8 +493,10 @@ void SettingsBox(
tr::now,
lt_delay,
FormatDelay(delay)));
Core::App().settings().setGroupCallPushToTalkDelay(delay);
applyAndSave();
if (Core::App().settings().groupCallPushToTalkDelay() != delay) {
Core::App().settings().setGroupCallPushToTalkDelay(delay);
applyAndSave();
}
};
callback(value);
const auto slider = pushToTalkInner->add(

View File

@@ -105,7 +105,9 @@ void Toasts::setupPinnedVideo() {
? _call->videoEndpointLargeValue()
: rpl::single(_call->videoEndpointLarge());
}) | rpl::flatten_latest(
) | rpl::start_with_next([=](const VideoEndpoint &endpoint) {
) | rpl::filter([=] {
return (_call->shownVideoTracks().size() > 1);
}) | rpl::start_with_next([=](const VideoEndpoint &endpoint) {
const auto pinned = _call->videoEndpointPinned();
const auto peer = endpoint.peer;
if (!peer) {
@@ -155,6 +157,8 @@ void Toasts::setupError() {
const auto key = [&] {
switch (error) {
case Error::NoCamera: return tr::lng_call_error_no_camera;
case Error::CameraFailed:
return tr::lng_group_call_failed_camera;
case Error::ScreenFailed:
return tr::lng_group_call_failed_screen;
case Error::MutedNoCamera:

View File

@@ -191,7 +191,7 @@ void Viewport::updateSelected(QPoint position) {
return;
}
for (const auto &tile : _tiles) {
const auto geometry = tile->shown()
const auto geometry = tile->visible()
? tile->geometry()
: QRect();
if (geometry.contains(position)) {
@@ -763,7 +763,12 @@ void Viewport::setTileGeometry(not_null<VideoTile*> tile, QRect geometry) {
const auto kMedium = style::ConvertScale(540);
const auto kSmall = style::ConvertScale(240);
const auto &endpoint = tile->endpoint();
const auto quality = (min >= kMedium)
const auto forceThumbnailQuality = !wide()
&& (ranges::count(_tiles, false, &VideoTile::hidden) > 1);
const auto forceFullQuality = wide() && (tile.get() == _large);
const auto quality = forceThumbnailQuality
? VideoQuality::Thumbnail
: (forceFullQuality || min >= kMedium)
? VideoQuality::Full
: (min >= kSmall)
? VideoQuality::Medium

View File

@@ -439,7 +439,7 @@ void Viewport::RendererGL::paint(
validateDatas();
auto index = 0;
for (const auto &tile : _owner->_tiles) {
if (!tile->shown()) {
if (!tile->visible()) {
index++;
continue;
}
@@ -988,6 +988,7 @@ void Viewport::RendererGL::bindFrame(
program.argb32->setUniformValue("s_texture", GLint(0));
} else {
const auto yuv = data.yuv420;
const auto format = Ui::GL::CurrentSingleComponentFormat();
program.yuv420->bind();
f.glActiveTexture(GL_TEXTURE0);
tileData.textures.bind(f, 0);
@@ -995,8 +996,8 @@ void Viewport::RendererGL::bindFrame(
f.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
uploadTexture(
f,
GL_RED,
GL_RED,
format,
format,
yuv->size,
tileData.textureSize,
yuv->y.stride,
@@ -1009,8 +1010,8 @@ void Viewport::RendererGL::bindFrame(
if (upload) {
uploadTexture(
f,
GL_RED,
GL_RED,
format,
format,
yuv->chromaSize,
tileData.textureChromaSize,
yuv->u.stride,
@@ -1021,8 +1022,8 @@ void Viewport::RendererGL::bindFrame(
if (upload) {
uploadTexture(
f,
GL_RED,
GL_RED,
format,
format,
yuv->chromaSize,
tileData.textureChromaSize,
yuv->v.stride,
@@ -1368,16 +1369,17 @@ void Viewport::RendererGL::validateNoiseTexture(
if (_noiseTexture.created()) {
return;
}
const auto format = Ui::GL::CurrentSingleComponentFormat();
_noiseTexture.ensureCreated(f, GL_NEAREST, GL_REPEAT);
_noiseTexture.bind(f, 0);
f.glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RED,
format,
kNoiseTextureSize,
kNoiseTextureSize,
0,
GL_RED,
format,
GL_UNSIGNED_BYTE,
nullptr);

View File

@@ -45,7 +45,7 @@ void Viewport::RendererSW::paintFallback(
tileData.stale = true;
}
for (const auto &tile : _owner->_tiles) {
if (!tile->shown()) {
if (!tile->visible()) {
continue;
}
paintTile(p, tile.get(), bounding, bg);

View File

@@ -82,14 +82,14 @@ bool Viewport::VideoTile::screencast() const {
void Viewport::VideoTile::setGeometry(
QRect geometry,
TileAnimation animation) {
_shown = true;
_hidden = false;
_geometry = geometry;
_animation = animation;
updateTopControlsPosition();
}
void Viewport::VideoTile::hide() {
_shown = false;
_hidden = true;
_quality = std::nullopt;
}
@@ -106,7 +106,7 @@ void Viewport::VideoTile::toggleTopControlsShown(bool shown) {
}
bool Viewport::VideoTile::updateRequestedQuality(VideoQuality quality) {
if (!_shown) {
if (_hidden) {
_quality = std::nullopt;
return false;
} else if (_quality && *_quality == quality) {
@@ -249,7 +249,7 @@ void Viewport::VideoTile::setup(rpl::producer<bool> pinned) {
}) | rpl::start_with_next([=](bool pinned) {
_pinned = pinned;
updateTopControlsSize();
if (_shown) {
if (!_hidden) {
updateTopControlsPosition();
_update();
}

View File

@@ -45,8 +45,11 @@ public:
[[nodiscard]] bool pinned() const {
return _pinned;
}
[[nodiscard]] bool shown() const {
return _shown && !_geometry.isEmpty();
[[nodiscard]] bool hidden() const {
return _hidden;
}
[[nodiscard]] bool visible() const {
return !_hidden && !_geometry.isEmpty();
}
[[nodiscard]] QRect pinOuter() const;
[[nodiscard]] QRect pinInner() const;
@@ -115,7 +118,7 @@ private:
Ui::Animations::Simple _topControlsShownAnimation;
bool _topControlsShown = false;
bool _pinned = false;
bool _shown = false;
bool _hidden = true;
std::optional<VideoQuality> _quality;
rpl::lifetime _lifetime;

View File

@@ -30,16 +30,12 @@ constexpr auto kVolumeStickedValues =
{ 25. / kMaxVolumePercent, 2. / kMaxVolumePercent },
{ 50. / kMaxVolumePercent, 2. / kMaxVolumePercent },
{ 75. / kMaxVolumePercent, 2. / kMaxVolumePercent },
{ 100. / kMaxVolumePercent, 5. / kMaxVolumePercent },
{ 100. / kMaxVolumePercent, 10. / kMaxVolumePercent },
{ 125. / kMaxVolumePercent, 2. / kMaxVolumePercent },
{ 150. / kMaxVolumePercent, 2. / kMaxVolumePercent },
{ 175. / kMaxVolumePercent, 2. / kMaxVolumePercent },
}};
QString VolumeString(int volumePercent) {
return u"%1%"_q.arg(volumePercent);
}
} // namespace
MenuVolumeItem::MenuVolumeItem(
@@ -75,20 +71,21 @@ MenuVolumeItem::MenuVolumeItem(
sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
const auto geometry = QRect(QPoint(), size);
_itemRect = geometry - _st.itemPadding;
_itemRect = geometry - st::groupCallMenuVolumePadding;
_speakerRect = QRect(_itemRect.topLeft(), _stCross.icon.size());
_arcPosition = _speakerRect.center()
+ QPoint(0, st::groupCallMenuSpeakerArcsSkip);
_volumeRect = QRect(
_arcPosition.x()
+ st::groupCallMenuVolumeSkip
+ _arcs->finishedWidth(),
const auto sliderLeft = _arcPosition.x()
+ st::groupCallMenuVolumeSkip
+ _arcs->maxWidth()
+ st::groupCallMenuVolumeSkip;
_slider->setGeometry(
st::groupCallMenuVolumeMargin.left(),
_speakerRect.y(),
_st.itemStyle.font->width(VolumeString(kMaxVolumePercent)),
(geometry.width()
- st::groupCallMenuVolumeMargin.left()
- st::groupCallMenuVolumeMargin.right()),
_speakerRect.height());
_slider->setGeometry(_itemRect
- style::margins(0, contentHeight() / 2, 0, 0));
}, lifetime());
setCloudVolume(startVolume);
@@ -110,15 +107,12 @@ MenuVolumeItem::MenuVolumeItem(
unmuteColor(),
muteColor(),
muteProgress);
p.setPen(mutePen);
p.setFont(_st.itemStyle.font);
p.drawText(_volumeRect, VolumeString(volume), style::al_left);
_crossLineMute->paint(
p,
_speakerRect.topLeft(),
muteProgress,
(!muteProgress) ? std::nullopt : std::optional<QColor>(mutePen));
(muteProgress > 0) ? std::make_optional(mutePen) : std::nullopt);
{
p.translate(_arcPosition);
@@ -133,7 +127,7 @@ MenuVolumeItem::MenuVolumeItem(
_toggleMuteLocallyRequests.fire_copy(newMuted);
_crossLineAnimation.start(
[=] { update(_speakerRect.united(_volumeRect)); },
[=] { update(_speakerRect); },
_localMuted ? 0. : 1.,
_localMuted ? 1. : 0.,
st::callPanelDuration);
@@ -141,8 +135,8 @@ MenuVolumeItem::MenuVolumeItem(
if (value > 0) {
_changeVolumeLocallyRequests.fire(value * _maxVolume);
}
update(_volumeRect);
_arcs->setValue(value);
updateSliderColor(value);
});
const auto returnVolume = [=] {
@@ -169,6 +163,7 @@ MenuVolumeItem::MenuVolumeItem(
if (!_cloudMuted && !muted) {
_changeVolumeRequests.fire_copy(newVolume);
}
updateSliderColor(value);
});
std::move(
@@ -209,30 +204,15 @@ MenuVolumeItem::MenuVolumeItem(
}
void MenuVolumeItem::initArcsAnimation() {
const auto volumeLeftWas = lifetime().make_state<int>(0);
const auto lastTime = lifetime().make_state<int>(0);
_arcsAnimation.init([=](crl::time now) {
_arcs->update(now);
update(_speakerRect);
const auto wasRect = _volumeRect;
_volumeRect.moveLeft(anim::interpolate(
*volumeLeftWas,
_arcPosition.x()
+ st::groupCallMenuVolumeSkip
+ _arcs->finishedWidth(),
std::clamp(
(now - (*lastTime))
/ float64(st::groupCallSpeakerArcsAnimation.duration),
0.,
1.)));
update(_speakerRect.united(wasRect.united(_volumeRect)));
});
_arcs->startUpdateRequests(
) | rpl::start_with_next([=] {
if (!_arcsAnimation.animating()) {
*volumeLeftWas = _volumeRect.left();
*lastTime = crl::now();
_arcsAnimation.start();
}
@@ -269,8 +249,30 @@ void MenuVolumeItem::setCloudVolume(int volume) {
}
void MenuVolumeItem::setSliderVolume(int volume) {
_slider->setValue(float64(volume) / _maxVolume);
update(_volumeRect);
const auto value = float64(volume) / _maxVolume;
_slider->setValue(value);
updateSliderColor(value);
}
void MenuVolumeItem::updateSliderColor(float64 value) {
value = std::clamp(value, 0., 1.);
const auto color = [](int rgb) {
return QColor(
int((rgb & 0xFF0000) >> 16),
int((rgb & 0x00FF00) >> 8),
int(rgb & 0x0000FF));
};
const auto colors = std::array<QColor, 4>{ {
color(0xF66464),
color(0xD0B738),
color(0x24CD80),
color(0x3BBCEC),
} };
_slider->setActiveFgOverride((value < 0.25)
? anim::color(colors[0], colors[1], value / 0.25)
: (value < 0.5)
? anim::color(colors[1], colors[2], (value - 0.25) / 0.25)
: anim::color(colors[2], colors[3], (value - 0.5) / 0.5));
}
not_null<QAction*> MenuVolumeItem::action() const {
@@ -282,9 +284,9 @@ bool MenuVolumeItem::isEnabled() const {
}
int MenuVolumeItem::contentHeight() const {
return _st.itemPadding.top()
+ _st.itemPadding.bottom()
+ _stCross.icon.height() * 2;
return st::groupCallMenuVolumePadding.top()
+ st::groupCallMenuVolumePadding.bottom()
+ _stCross.icon.height();
}
rpl::producer<bool> MenuVolumeItem::toggleMuteRequests() const {

View File

@@ -52,6 +52,7 @@ private:
void setCloudVolume(int volume);
void setSliderVolume(int volume);
void updateSliderColor(float64 value);
QColor unmuteColor() const;
QColor muteColor() const;
@@ -64,7 +65,6 @@ private:
QRect _itemRect;
QRect _speakerRect;
QRect _volumeRect;
QPoint _arcPosition;
const base::unique_qptr<Ui::MediaSlider> _slider;

View File

@@ -171,11 +171,11 @@ bool BotKeyboard::moderateKeyActivate(int key) {
App::sendBotCommand(user, user, qsl("/pattern"));
} else if (key == Qt::Key_4) {
App::sendBotCommand(user, user, qsl("/abuse"));
} else if (key == Qt::Key_0 || key == Qt::Key_E) {
} else if (key == Qt::Key_0) {
App::sendBotCommand(user, user, qsl("/undo"));
} else if (key == Qt::Key_Plus || key == Qt::Key_QuoteLeft) {
} else if (key == Qt::Key_7) {
App::sendBotCommand(user, user, qsl("/next"));
} else if (key == Qt::Key_Period || key == Qt::Key_S) {
} else if (key == Qt::Key_8) {
App::sendBotCommand(user, user, qsl("/stats"));
}
return true;
@@ -201,6 +201,7 @@ bool BotKeyboard::updateMarkup(HistoryItem *to, bool force) {
if (_wasForMsgId.msg) {
_maximizeSize = _singleUse = _forceReply = false;
_wasForMsgId = FullMsgId();
_placeholder = QString();
_impl = nullptr;
return true;
}
@@ -218,6 +219,12 @@ bool BotKeyboard::updateMarkup(HistoryItem *to, bool force) {
_maximizeSize = !(markupFlags & MTPDreplyKeyboardMarkup::Flag::f_resize);
_singleUse = _forceReply || (markupFlags & MTPDreplyKeyboardMarkup::Flag::f_single_use);
if (const auto markup = to->Get<HistoryMessageReplyMarkup>()) {
_placeholder = markup->placeholder;
} else {
_placeholder = QString();
}
_impl = nullptr;
if (auto markup = to->Get<HistoryMessageReplyMarkup>()) {
if (!markup->rows.empty()) {

View File

@@ -31,8 +31,12 @@ public:
// With force=true the markup is updated even if it is
// already shown for the passed history item.
bool updateMarkup(HistoryItem *last, bool force = false);
bool hasMarkup() const;
bool forceReply() const;
[[nodiscard]] bool hasMarkup() const;
[[nodiscard]] bool forceReply() const;
[[nodiscard]] QString placeholder() const {
return _placeholder;
}
void step_selected(crl::time ms, bool timer);
void resizeToWidth(int newWidth, int maxOuterHeight) {
@@ -40,10 +44,10 @@ public:
return TWidget::resizeToWidth(newWidth);
}
bool maximizeSize() const;
bool singleUse() const;
[[nodiscard]] bool maximizeSize() const;
[[nodiscard]] bool singleUse() const;
FullMsgId forMsgId() const {
[[nodiscard]] FullMsgId forMsgId() const {
return _wasForMsgId;
}
@@ -76,6 +80,7 @@ private:
const not_null<Main::Session*> _session;
FullMsgId _wasForMsgId;
QString _placeholder;
int _height = 0;
int _maxOuterHeight = 0;
bool _maximizeSize = false;

View File

@@ -15,10 +15,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/animations.h"
#include "ui/effects/radial_animation.h"
#include "ui/emoji_config.h"
#include "ui/ui_utility.h"
#include "core/application.h"
#include "main/main_account.h"
#include "mainwidget.h"
#include "app.h"
#include "storage/storage_cloud_blob.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
@@ -456,7 +456,7 @@ void Row::setupPreview(const Set &set) {
const auto full = original.height();
auto &&preview = ranges::views::zip(_preview, ranges::views::ints(0, int(_preview.size())));
for (auto &&[pixmap, index] : preview) {
pixmap = App::pixmapFromImageInPlace(original.copy(
pixmap = Ui::PixmapFromImage(original.copy(
{ full * index, 0, full, full }
).scaledToWidth(size, Qt::SmoothTransformation));
pixmap.setDevicePixelRatio(cRetinaFactor());

View File

@@ -29,7 +29,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/input_fields.h"
#include "ui/text/text_options.h"
#include "ui/image/image.h"
#include "ui/effects/path_shift_gradient.h"
#include "ui/ui_utility.h"
#include "ui/cached_round_corners.h"
#include "base/unixtime.h"
@@ -125,6 +127,8 @@ private:
bool _isOneColumn = false;
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
Fn<SendMenu::Type()> _sendMenuType;
rpl::event_stream<FieldAutocomplete::MentionChosen> _mentionChosen;
@@ -438,23 +442,24 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
} else if (_type == Type::BotCommands) {
bool listAllSuggestions = _filter.isEmpty();
bool hasUsername = _filter.indexOf('@') > 0;
base::flat_map<UserData*, bool> bots;
base::flat_map<
not_null<UserData*>,
not_null<const std::vector<BotCommand>*>> bots;
int32 cnt = 0;
if (_chat) {
if (_chat->noParticipantInfo()) {
_chat->session().api().requestFullPeer(_chat);
} else if (!_chat->participants.empty()) {
const auto &commands = _chat->botCommands();
for (const auto user : _chat->participants) {
if (!user->isBot()) {
continue;
} else if (!user->botInfo->inited) {
user->session().api().requestFullPeer(user);
}
if (user->botInfo->commands.isEmpty()) {
continue;
const auto i = commands.find(peerToUser(user->id));
if (i != end(commands)) {
bots.emplace(user, &i->second);
cnt += i->second.size();
}
bots.emplace(user, true);
cnt += user->botInfo->commands.size();
}
}
} else if (_user && _user->isBot()) {
@@ -462,65 +467,71 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
_user->session().api().requestFullPeer(_user);
}
cnt = _user->botInfo->commands.size();
bots.emplace(_user, true);
bots.emplace(_user, &_user->botInfo->commands);
} else if (_channel && _channel->isMegagroup()) {
if (_channel->mgInfo->bots.empty()) {
if (!_channel->mgInfo->botStatus) {
_channel->session().api().requestBots(_channel);
}
} else {
const auto &commands = _channel->mgInfo->botCommands();
for (const auto user : _channel->mgInfo->bots) {
if (!user->isBot()) {
continue;
} else if (!user->botInfo->inited) {
user->session().api().requestFullPeer(user);
}
if (user->botInfo->commands.isEmpty()) {
continue;
const auto i = commands.find(peerToUser(user->id));
if (i != end(commands)) {
bots.emplace(user, &i->second);
cnt += i->second.size();
}
bots.emplace(user, true);
cnt += user->botInfo->commands.size();
}
}
}
if (cnt) {
const auto make = [&](
not_null<UserData*> user,
const BotCommand &command) {
return BotCommandRow{
user,
command.command,
command.description,
user->activeUserpicView()
};
};
brows.reserve(cnt);
int32 botStatus = _chat ? _chat->botStatus : ((_channel && _channel->isMegagroup()) ? _channel->mgInfo->botStatus : -1);
if (_chat) {
for (const auto &user : _chat->lastAuthors) {
if (!user->isBot()) {
continue;
} else if (!bots.contains(user)) {
continue;
} else if (!user->botInfo->inited) {
user->session().api().requestFullPeer(user);
}
if (user->botInfo->commands.isEmpty()) {
const auto i = bots.find(user);
if (i == end(bots)) {
continue;
}
bots.remove(user);
for (auto j = 0, l = user->botInfo->commands.size(); j != l; ++j) {
for (const auto &command : *i->second) {
if (!listAllSuggestions) {
auto toFilter = (hasUsername || botStatus == 0 || botStatus == 2)
? user->botInfo->commands.at(j).command + '@' + user->username
: user->botInfo->commands.at(j).command;
? command.command + '@' + user->username
: command.command;
if (!toFilter.startsWith(_filter, Qt::CaseInsensitive)/* || toFilter.size() == _filter.size()*/) {
continue;
}
}
brows.push_back({ user, &user->botInfo->commands.at(j) });
brows.push_back(make(user, command));
}
bots.erase(i);
}
}
if (!bots.empty()) {
for (auto i = bots.cbegin(), e = bots.cend(); i != e; ++i) {
UserData *user = i->first;
for (int32 j = 0, l = user->botInfo->commands.size(); j < l; ++j) {
const auto user = i->first;
for (const auto &command : *i->second) {
if (!listAllSuggestions) {
QString toFilter = (hasUsername || botStatus == 0 || botStatus == 2) ? user->botInfo->commands.at(j).command + '@' + user->username : user->botInfo->commands.at(j).command;
QString toFilter = (hasUsername || botStatus == 0 || botStatus == 2) ? command.command + '@' + user->username : command.command;
if (!toFilter.startsWith(_filter, Qt::CaseInsensitive)/* || toFilter.size() == _filter.size()*/) continue;
}
brows.push_back({ user, &user->botInfo->commands.at(j) });
brows.push_back(make(user, command));
}
}
}
@@ -738,13 +749,17 @@ FieldAutocomplete::Inner::Inner(
, _hrows(hrows)
, _brows(brows)
, _srows(srows)
, _pathGradient(std::make_unique<Ui::PathShiftGradient>(
st::windowBgRipple,
st::windowBgOver,
[=] { update(); }))
, _previewTimer([=] { showPreview(); }) {
controller->session().downloaderTaskFinished(
) | rpl::start_with_next([=] {
update();
}, lifetime());
controller->adaptive().changed(
controller->adaptive().value(
) | rpl::start_with_next([=] {
_isOneColumn = controller->adaptive().isOneColumn();
update();
@@ -770,6 +785,11 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
- st::defaultScrollArea.width;
if (!_srows->empty()) {
_pathGradient->startFrame(
0,
width(),
std::min(st::msgMaxWidth / 2, width() / 2));
int32 rows = rowscount(_srows->size(), _stickersPerRow);
int32 fromrow = floorclamp(r.y() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows);
int32 torow = ceilclamp(r.y() + r.height() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows);
@@ -815,6 +835,7 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
w = std::max(qRound(coef * document->dimensions.width()), 1);
h = std::max(qRound(coef * document->dimensions.height()), 1);
}
if (sticker.animated && sticker.animated->ready()) {
const auto frame = sticker.animated->frame();
const auto size = frame.size() / cIntRetinaFactor();
@@ -832,6 +853,13 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
} else if (const auto image = media->getStickerSmall()) {
QPoint ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2);
p.drawPixmapLeft(ppos, width(), image->pix(w, h));
} else {
QPoint ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2);
ChatHelpers::PaintStickerThumbnailPath(
p,
media.get(),
QRect(ppos, QSize(w, h)),
_pathGradient.get());
}
}
}
@@ -920,8 +948,7 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
auto &row = _brows->at(i);
const auto user = row.user;
const auto command = row.command;
auto toHighlight = command->command;
auto toHighlight = row.command;
int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : ((_parent->channel() && _parent->channel()->isMegagroup()) ? _parent->channel()->mgInfo->botStatus : -1);
if (hasUsername || botStatus == 0 || botStatus == 2) {
toHighlight += '@' + user->username;
@@ -939,9 +966,16 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
auto addleft = commandTextWidth + st::mentionPadding.left();
auto widthleft = mentionwidth - addleft;
if (widthleft > st::mentionFont->elidew && !command->descriptionText().isEmpty()) {
if (!row.description.isEmpty()
&& row.descriptionText.isEmpty()) {
row.descriptionText.setText(
st::defaultTextStyle,
row.description,
Ui::NameTextOptions());
}
if (widthleft > st::mentionFont->elidew && !row.descriptionText.isEmpty()) {
p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p);
command->descriptionText().drawElided(p, mentionleft + addleft, i * st::mentionHeight + st::mentionTop, widthleft);
row.descriptionText.drawElided(p, mentionleft + addleft, i * st::mentionHeight + st::mentionTop, widthleft);
}
}
}
@@ -1043,7 +1077,7 @@ bool FieldAutocomplete::Inner::chooseAtIndex(
} else if (!_brows->empty()) {
if (index < _brows->size()) {
const auto user = _brows->at(index).user;
const auto command = _brows->at(index).command;
const auto &command = _brows->at(index).command;
const auto botStatus = _parent->chat()
? _parent->chat()->botStatus
: ((_parent->channel() && _parent->channel()->isMegagroup())
@@ -1054,7 +1088,7 @@ bool FieldAutocomplete::Inner::chooseAtIndex(
|| botStatus == 2
|| _parent->filter().indexOf('@') > 0);
const auto commandString = QString("/%1%2").arg(
command->command,
command,
insertUsername ? ('@' + user->username) : QString());
_botCommandChosen.fire({ commandString, method });

View File

@@ -136,8 +136,10 @@ private:
struct BotCommandRow {
not_null<UserData*> user;
not_null<const BotCommand*> command;
QString command;
QString description;
std::shared_ptr<Data::CloudImageView> userpic;
Ui::Text::String descriptionText;
};
using HashtagRows = std::vector<QString>;

View File

@@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/popup_menu.h"
#include "ui/effects/animations.h"
#include "ui/effects/ripple_animation.h"
#include "ui/effects/path_shift_gradient.h"
#include "ui/image/image.h"
#include "ui/cached_round_corners.h"
#include "lottie/lottie_multi_player.h"
@@ -110,7 +111,9 @@ struct StickerIcon {
class StickersListWidget::Footer : public TabbedSelector::InnerFooter {
public:
explicit Footer(not_null<StickersListWidget*> parent);
explicit Footer(
not_null<StickersListWidget*> parent,
bool searchButtonVisible);
void preloadImages();
void validateSelectedIcon(
@@ -127,6 +130,8 @@ public:
void clearHeavyData();
rpl::producer<> openSettingsRequests() const;
protected:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
@@ -145,8 +150,7 @@ private:
};
using OverState = std::variant<SpecialOver, int>;
template <typename Callback>
void enumerateVisibleIcons(Callback callback);
void enumerateVisibleIcons(Fn<void(const StickerIcon &, int)> callback);
bool iconsAnimationCallback(crl::time now);
void setSelectedIcon(
@@ -170,6 +174,7 @@ private:
void scrollByWheelEvent(not_null<QWheelEvent*> e);
const not_null<StickersListWidget*> _pan;
const bool _searchButtonVisible = true;
static constexpr auto kVisibleIconsCount = 8;
@@ -196,6 +201,8 @@ private:
object_ptr<Ui::CrossButton> _searchCancel = { nullptr };
QPointer<QWidget> _focusTakenFrom;
rpl::event_stream<> _openSettingsRequests;
};
auto StickersListWidget::PrepareStickers(
@@ -240,15 +247,21 @@ void StickersListWidget::Sticker::ensureMediaCreated() {
documentMedia = document->createMediaView();
}
StickersListWidget::Footer::Footer(not_null<StickersListWidget*> parent)
StickersListWidget::Footer::Footer(
not_null<StickersListWidget*> parent,
bool searchButtonVisible)
: InnerFooter(parent)
, _pan(parent)
, _searchButtonVisible(searchButtonVisible)
, _iconsAnimation([=](crl::time now) {
return iconsAnimationCallback(now);
}) {
setMouseTracking(true);
_iconsLeft = _iconsRight = st::emojiCategorySkip + st::stickerIconWidth;
_iconsLeft = st::emojiCategorySkip + (_searchButtonVisible
? st::stickerIconWidth
: 0);
_iconsRight = st::emojiCategorySkip + st::stickerIconWidth;
_pan->session().downloaderTaskFinished(
) | rpl::start_with_next([=] {
@@ -340,13 +353,17 @@ void StickersListWidget::Footer::returnFocus() {
}
}
template <typename Callback>
void StickersListWidget::Footer::enumerateVisibleIcons(Callback callback) {
void StickersListWidget::Footer::enumerateVisibleIcons(
Fn<void(const StickerIcon &, int)> callback) {
auto iconsX = qRound(_iconsX.current());
auto index = iconsX / st::stickerIconWidth;
auto x = _iconsLeft - (iconsX % st::stickerIconWidth);
auto first = floorclamp(iconsX, st::stickerIconWidth, 0, _icons.size());
auto last = ceilclamp(iconsX + width(), st::stickerIconWidth, 0, _icons.size());
auto last = ceilclamp(
iconsX + width(),
st::stickerIconWidth,
0,
_icons.size());
for (auto index = first; index != last; ++index) {
callback(_icons[index], x);
x += st::stickerIconWidth;
@@ -526,6 +543,10 @@ void StickersListWidget::Footer::resizeSearchControls() {
_searchCancel->moveToRight(st::gifsSearchCancelPosition.x(), st::gifsSearchCancelPosition.y());
}
rpl::producer<> StickersListWidget::Footer::openSettingsRequests() const {
return _openSettingsRequests.events();
}
void StickersListWidget::Footer::mousePressEvent(QMouseEvent *e) {
if (e->button() != Qt::LeftButton) {
return;
@@ -534,11 +555,7 @@ void StickersListWidget::Footer::mousePressEvent(QMouseEvent *e) {
updateSelected();
if (_iconOver == SpecialOver::Settings) {
_pan->controller()->show(Box<StickersBox>(
_pan->controller(),
(hasOnlyFeaturedSets()
? StickersBox::Section::Featured
: StickersBox::Section::Installed)));
_openSettingsRequests.fire({});
} else if (_iconOver == SpecialOver::Search) {
toggleSearch(true);
} else {
@@ -668,7 +685,8 @@ void StickersListWidget::Footer::updateSelected() {
const auto settingsLeft = width() - _iconsRight;
const auto searchLeft = _iconsLeft - st::stickerIconWidth;
auto newOver = OverState(SpecialOver::None);
if (x >= searchLeft
if (_searchButtonVisible
&& x >= searchLeft
&& x < searchLeft + st::stickerIconWidth
&& y >= _iconsTop
&& y < _iconsTop + st::emojiFooterHeight) {
@@ -752,12 +770,21 @@ bool StickersListWidget::Footer::hasOnlyFeaturedSets() const {
void StickersListWidget::Footer::paintStickerSettingsIcon(Painter &p) const {
const auto settingsLeft = width() - _iconsRight;
st::stickersSettings.paint(p, settingsLeft + (st::stickerIconWidth - st::stickersSettings.width()) / 2, _iconsTop + st::emojiCategory.iconPosition.y(), width());
st::stickersSettings.paint(
p,
settingsLeft
+ (st::stickerIconWidth - st::stickersSettings.width()) / 2,
_iconsTop + st::emojiCategory.iconPosition.y(),
width());
}
void StickersListWidget::Footer::paintSearchIcon(Painter &p) const {
const auto searchLeft = _iconsLeft - st::stickerIconWidth;
st::stickersSearch.paint(p, searchLeft + (st::stickerIconWidth - st::stickersSearch.width()) / 2, _iconsTop + st::emojiCategory.iconPosition.y(), width());
st::stickersSearch.paint(
p,
searchLeft + (st::stickerIconWidth - st::stickersSearch.width()) / 2,
_iconsTop + st::emojiCategory.iconPosition.y(),
width());
}
void StickersListWidget::Footer::validateIconLottieAnimation(
@@ -895,10 +922,16 @@ bool StickersListWidget::Footer::iconsAnimationCallback(crl::time now) {
StickersListWidget::StickersListWidget(
QWidget *parent,
not_null<Window::SessionController*> controller)
not_null<Window::SessionController*> controller,
bool masks)
: Inner(parent, controller)
, _api(&controller->session().mtp())
, _section(Section::Stickers)
, _isMasks(masks)
, _pathGradient(std::make_unique<Ui::PathShiftGradient>(
st::windowBgRipple,
st::windowBgOver,
[=] { update(); }))
, _megagroupSetAbout(st::columnMinimalWidthThird - st::emojiScroll.width - st::emojiPanHeaderLeft)
, _addText(tr::lng_stickers_featured_add(tr::now).toUpper())
, _addWidth(st::stickersTrendingAdd.font->width(_addText))
@@ -909,8 +942,10 @@ StickersListWidget::StickersListWidget(
setAttribute(Qt::WA_OpaquePaintEvent);
_settings->addClickHandler([=] {
using Section = StickersBox::Section;
controller->show(
Box<StickersBox>(controller, StickersBox::Section::Installed));
Box<StickersBox>(controller, Section::Installed, _isMasks),
Ui::LayerOption::KeepOther);
});
session().downloaderTaskFinished(
@@ -930,7 +965,11 @@ StickersListWidget::StickersListWidget(
}, lifetime());
session().data().stickers().recentUpdated(
) | rpl::start_with_next([=] {
) | rpl::start_with_next([=](Data::Stickers::Recent recent) {
const auto attached = (recent == Data::Stickers::Recent::Attached);
if (attached != _isMasks) {
return;
}
refreshRecent();
}, lifetime());
}
@@ -954,8 +993,22 @@ rpl::producer<> StickersListWidget::checkForHide() const {
object_ptr<TabbedSelector::InnerFooter> StickersListWidget::createFooter() {
Expects(_footer == nullptr);
auto result = object_ptr<Footer>(this);
auto result = object_ptr<Footer>(this, !_isMasks);
_footer = result;
_footer->openSettingsRequests(
) | rpl::start_with_next([=] {
const auto onlyFeatured = _footer->hasOnlyFeaturedSets();
Ui::show(Box<StickersBox>(
controller(),
(onlyFeatured
? StickersBox::Section::Featured
: _isMasks
? StickersBox::Section::Masks
: StickersBox::Section::Installed),
onlyFeatured ? false : _isMasks),
Ui::LayerOption::KeepOther);
}, _footer->lifetime());
return result;
}
@@ -1531,6 +1584,8 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
toColumn = _columnCount - toColumn;
}
_pathGradient->startFrame(0, width(), width() / 2);
auto &sets = shownSets();
auto selectedSticker = std::get_if<OverSticker>(&_selected);
auto selectedButton = std::get_if<OverButton>(!v::is_null(_pressed)
@@ -1889,6 +1944,7 @@ void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int section,
h = std::max(qRound(coef * document->dimensions.height()), 1);
}
auto ppos = pos + QPoint((_singleSize.width() - w) / 2, (_singleSize.height() - h) / 2);
if (sticker.animated && sticker.animated->ready()) {
auto request = Lottie::FrameRequest();
request.box = boundingBoxSize() * cIntRetinaFactor();
@@ -1918,6 +1974,12 @@ void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int section,
if (sticker.savedFrame.isNull()) {
sticker.savedFrame = pixmap;
}
} else {
PaintStickerThumbnailPath(
p,
media.get(),
QRect(ppos, QSize{ w, h }),
_pathGradient.get());
}
}
@@ -2363,12 +2425,12 @@ void StickersListWidget::refreshStickers() {
void StickersListWidget::refreshMySets() {
auto wasSets = base::take(_mySets);
_favedStickersMap.clear();
_mySets.reserve(session().data().stickers().setsOrder().size() + 3);
_mySets.reserve(defaultSetsOrder().size() + 3);
refreshFavedStickers();
refreshRecentStickers(false);
refreshMegagroupStickers(GroupStickersPlace::Visible);
for (const auto setId : session().data().stickers().setsOrder()) {
for (const auto setId : defaultSetsOrder()) {
const auto externalLayout = false;
appendSet(_mySets, setId, externalLayout, AppendSkip::Archived);
}
@@ -2441,7 +2503,9 @@ void StickersListWidget::refreshSearchIndex() {
}
void StickersListWidget::refreshSettingsVisibility() {
const auto visible = (_section == Section::Stickers) && _mySets.empty();
const auto visible = (_section == Section::Stickers)
&& _mySets.empty()
&& !_isMasks;
_settings->setVisible(visible);
}
@@ -2519,9 +2583,15 @@ auto StickersListWidget::collectRecentStickers() -> std::vector<Sticker> {
auto result = std::vector<Sticker>();
const auto &sets = session().data().stickers().sets();
const auto &recent = session().data().stickers().getRecentPack();
const auto customIt = sets.find(Data::Stickers::CustomSetId);
const auto cloudIt = sets.find(Data::Stickers::CloudRecentSetId);
const auto &recent = _isMasks
? RecentStickerPack()
: session().data().stickers().getRecentPack();
const auto customIt = _isMasks
? sets.cend()
: sets.find(Data::Stickers::CustomSetId);
const auto cloudIt = sets.find(_isMasks
? Data::Stickers::CloudRecentAttachedSetId
: Data::Stickers::CloudRecentSetId);
const auto customCount = (customIt != sets.cend())
? customIt->second->stickers.size()
: 0;
@@ -2607,6 +2677,9 @@ void StickersListWidget::refreshRecentStickers(bool performResize) {
}
void StickersListWidget::refreshFavedStickers() {
if (_isMasks) {
return;
}
clearSelection();
const auto &sets = session().data().stickers().sets();
const auto it = sets.find(Data::Stickers::FavedSetId);
@@ -2634,7 +2707,7 @@ void StickersListWidget::refreshFavedStickers() {
}
void StickersListWidget::refreshMegagroupStickers(GroupStickersPlace place) {
if (!_megagroupSet) {
if (!_megagroupSet || _isMasks) {
return;
}
auto canEdit = _megagroupSet->canEditStickers();
@@ -2720,7 +2793,7 @@ void StickersListWidget::refreshMegagroupStickers(GroupStickersPlace place) {
std::vector<StickerIcon> StickersListWidget::fillIcons() {
auto result = std::vector<StickerIcon>();
result.reserve(_mySets.size() + 1);
if (!_officialSets.empty()) {
if (!_officialSets.empty() && !_isMasks) {
result.emplace_back(Data::Stickers::FeaturedSetId);
}
@@ -2837,7 +2910,8 @@ bool StickersListWidget::setHasTitle(const Set &set) const {
if (set.id == Data::Stickers::FavedSetId) {
return false;
} else if (set.id == Data::Stickers::RecentSetId) {
return !_mySets.empty() && _mySets[0].id == Data::Stickers::FavedSetId;
return !_mySets.empty()
&& (_isMasks || (_mySets[0].id == Data::Stickers::FavedSetId));
}
return true;
}
@@ -3101,53 +3175,92 @@ void StickersListWidget::removeMegagroupSet(bool locally) {
void StickersListWidget::removeSet(uint64 setId) {
const auto &sets = session().data().stickers().sets();
const auto it = sets.find(setId);
if (it != sets.cend()) {
const auto set = it->second.get();
_removingSetId = set->id;
auto text = tr::lng_stickers_remove_pack(tr::now, lt_sticker_pack, set->title);
controller()->show(Box<ConfirmBox>(text, tr::lng_stickers_remove_pack_confirm(tr::now), crl::guard(this, [=] {
Ui::hideLayer();
const auto &sets = session().data().stickers().sets();
const auto it = sets.find(_removingSetId);
if (it != sets.cend()) {
const auto set = it->second.get();
if (set->id && set->access) {
_api.request(MTPmessages_UninstallStickerSet(MTP_inputStickerSetID(MTP_long(set->id), MTP_long(set->access)))).send();
} else if (!set->shortName.isEmpty()) {
_api.request(MTPmessages_UninstallStickerSet(MTP_inputStickerSetShortName(MTP_string(set->shortName)))).send();
}
auto writeRecent = false;
auto &recent = session().data().stickers().getRecentPack();
for (auto i = recent.begin(); i != recent.cend();) {
if (set->stickers.indexOf(i->first) >= 0) {
i = recent.erase(i);
writeRecent = true;
} else {
++i;
}
}
set->flags &= ~MTPDstickerSet::Flag::f_installed_date;
set->installDate = TimeId(0);
//
// Set can be in search results.
//
//if (!(set->flags & MTPDstickerSet_ClientFlag::f_featured)
// && !(set->flags & MTPDstickerSet_ClientFlag::f_special)) {
// sets.erase(it);
//}
int removeIndex = session().data().stickers().setsOrder().indexOf(_removingSetId);
if (removeIndex >= 0) session().data().stickers().setsOrderRef().removeAt(removeIndex);
refreshStickers();
session().local().writeInstalledStickers();
if (writeRecent) session().saveSettings();
}
_removingSetId = 0;
_checkForHide.fire({});
}), crl::guard(this, [=] {
_removingSetId = 0;
_checkForHide.fire({});
})));
if (it == sets.cend()) {
return;
}
const auto set = it->second.get();
_removingSetId = set->id;
const auto text = tr::lng_stickers_remove_pack(
tr::now,
lt_sticker_pack,
set->title);
const auto confirm = tr::lng_stickers_remove_pack_confirm(tr::now);
controller()->show(Box<ConfirmBox>(text, confirm, crl::guard(this, [=](
Fn<void()> &&close) {
close();
const auto &sets = session().data().stickers().sets();
const auto it = sets.find(_removingSetId);
if (it != sets.cend()) {
const auto set = it->second.get();
if (set->id && set->access) {
_api.request(MTPmessages_UninstallStickerSet(
MTP_inputStickerSetID(
MTP_long(set->id),
MTP_long(set->access)))
).send();
} else if (!set->shortName.isEmpty()) {
_api.request(MTPmessages_UninstallStickerSet(
MTP_inputStickerSetShortName(
MTP_string(set->shortName)))
).send();
}
auto writeRecent = false;
auto &recent = session().data().stickers().getRecentPack();
for (auto i = recent.begin(); i != recent.cend();) {
if (set->stickers.indexOf(i->first) >= 0) {
i = recent.erase(i);
writeRecent = true;
} else {
++i;
}
}
set->flags &= ~MTPDstickerSet::Flag::f_installed_date;
set->installDate = TimeId(0);
//
// Set can be in search results.
//
//if (!(set->flags & MTPDstickerSet_ClientFlag::f_featured)
// && !(set->flags & MTPDstickerSet_ClientFlag::f_special)) {
// sets.erase(it);
//}
const auto removeIndex = defaultSetsOrder().indexOf(
_removingSetId);
if (removeIndex >= 0) {
defaultSetsOrderRef().removeAt(removeIndex);
}
refreshStickers();
if (set->flags & MTPDstickerSet::Flag::f_masks) {
session().local().writeInstalledMasks();
} else {
session().local().writeInstalledStickers();
}
if (writeRecent) {
session().saveSettings();
}
session().data().stickers().notifyUpdated();
}
_removingSetId = 0;
_checkForHide.fire({});
}), crl::guard(this, [=] {
_removingSetId = 0;
_checkForHide.fire({});
})), Ui::LayerOption::KeepOther);
}
const Data::StickersSetsOrder &StickersListWidget::defaultSetsOrder() const {
return !_isMasks
? session().data().stickers().setsOrder()
: session().data().stickers().maskSetsOrder();
}
Data::StickersSetsOrder &StickersListWidget::defaultSetsOrderRef() {
return !_isMasks
? session().data().stickers().setsOrderRef()
: session().data().stickers().maskSetsOrderRef();
}
bool StickersListWidget::mySetsEmpty() const {
return _mySets.empty();
}
StickersListWidget::~StickersListWidget() = default;

View File

@@ -25,6 +25,7 @@ class LinkButton;
class PopupMenu;
class RippleAnimation;
class BoxContent;
class PathShiftGradient;
} // namespace Ui
namespace Lottie {
@@ -48,7 +49,8 @@ class StickersListWidget
public:
StickersListWidget(
QWidget *parent,
not_null<Window::SessionController*> controller);
not_null<Window::SessionController*> controller,
bool masks = false);
Main::Session &session() const;
@@ -87,6 +89,8 @@ public:
not_null<Ui::PopupMenu*> menu,
SendMenu::Type type) override;
bool mySetsEmpty() const;
~StickersListWidget();
protected:
@@ -299,6 +303,9 @@ private:
void refreshMegagroupSetGeometry();
QRect megagroupSetButtonRectFinal() const;
const Data::StickersSetsOrder &defaultSetsOrder() const;
Data::StickersSetsOrder &defaultSetsOrderRef();
enum class AppendSkip {
None,
Archived,
@@ -348,6 +355,7 @@ private:
int _officialOffset = 0;
Section _section = Section::Stickers;
const bool _isMasks;
bool _displayingSet = false;
uint64 _removingSetId = 0;
@@ -361,6 +369,8 @@ private:
OverState _pressed;
QPoint _lastMousePosition;
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
Ui::Text::String _megagroupSetAbout;
QString _megagroupSetButtonText;
int _megagroupSetButtonTextWidth = 0;

View File

@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_file_origin.h"
#include "storage/cache/storage_cache_database.h"
#include "ui/effects/path_shift_gradient.h"
#include "main/main_session.h"
namespace ChatHelpers {
@@ -188,4 +189,50 @@ std::unique_ptr<Lottie::SinglePlayer> LottieThumbnail(
box);
}
bool PaintStickerThumbnailPath(
QPainter &p,
not_null<Data::DocumentMedia*> media,
QRect target,
QLinearGradient *gradient) {
const auto &path = media->thumbnailPath();
const auto dimensions = media->owner()->dimensions;
if (path.isEmpty() || dimensions.isEmpty() || target.isEmpty()) {
return false;
}
p.save();
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.translate(target.topLeft());
if (gradient) {
const auto scale = dimensions.width() / float64(target.width());
const auto shift = p.worldTransform().dx();
gradient->setStart((gradient->start().x() - shift) * scale, 0);
gradient->setFinalStop(
(gradient->finalStop().x() - shift) * scale,
0);
p.setBrush(*gradient);
}
p.scale(
target.width() / float64(dimensions.width()),
target.height() / float64(dimensions.height()));
p.drawPath(path);
p.restore();
return true;
}
bool PaintStickerThumbnailPath(
QPainter &p,
not_null<Data::DocumentMedia*> media,
QRect target,
not_null<Ui::PathShiftGradient*> gradient) {
return gradient->paint([&](const Ui::PathShiftGradient::Background &bg) {
if (const auto color = std::get_if<style::color>(&bg)) {
p.setBrush(*color);
return PaintStickerThumbnailPath(p, media, target);
}
const auto gradient = v::get<QLinearGradient*>(bg);
return PaintStickerThumbnailPath(p, media, target, gradient);
});
}
} // namespace ChatHelpers

View File

@@ -26,6 +26,10 @@ namespace Main {
class Session;
} // namespace Main
namespace Ui {
class PathShiftGradient;
} // namespace Ui
namespace Data {
class DocumentMedia;
class StickersSetThumbnailView;
@@ -71,4 +75,16 @@ enum class StickerLottieSize : uchar {
QSize box,
std::shared_ptr<Lottie::FrameRenderer> renderer = nullptr);
bool PaintStickerThumbnailPath(
QPainter &p,
not_null<Data::DocumentMedia*> media,
QRect target,
QLinearGradient *gradient = nullptr);
bool PaintStickerThumbnailPath(
QPainter &p,
not_null<Data::DocumentMedia*> media,
QRect target,
not_null<Ui::PathShiftGradient*> gradient);
} // namespace ChatHelpers

View File

@@ -256,8 +256,12 @@ void TabbedSelector::SlideAnimation::paintFrame(QPainter &p, float64 dt, float64
p.drawImage(outerLeft / cIntRetinaFactor(), outerTop / cIntRetinaFactor(), _frame, outerLeft, outerTop, outerRight - outerLeft, outerBottom - outerTop);
}
TabbedSelector::Tab::Tab(SelectorTab type, object_ptr<Inner> widget)
TabbedSelector::Tab::Tab(
SelectorTab type,
int index,
object_ptr<Inner> widget)
: _type(type)
, _index(index)
, _widget(std::move(widget))
, _weak(_widget)
, _footer(_widget ? _widget->createFooter() : nullptr) {
@@ -292,33 +296,52 @@ TabbedSelector::TabbedSelector(
, _topShadow(full() ? object_ptr<Ui::PlainShadow>(this) : nullptr)
, _bottomShadow(this)
, _scroll(this, st::emojiScroll)
, _tabs { {
createTab(SelectorTab::Emoji),
createTab(SelectorTab::Stickers),
createTab(SelectorTab::Gifs),
} }
, _tabs([&] {
std::vector<Tab> tabs;
if (full()) {
tabs.reserve(3);
tabs.push_back(createTab(SelectorTab::Emoji, 0));
tabs.push_back(createTab(SelectorTab::Stickers, 1));
tabs.push_back(createTab(SelectorTab::Gifs, 2));
} else if (mediaEditor()) {
tabs.reserve(2);
tabs.push_back(createTab(SelectorTab::Stickers, 0));
tabs.push_back(createTab(SelectorTab::Masks, 1));
} else {
tabs.reserve(1);
tabs.push_back(createTab(SelectorTab::Emoji, 0));
}
return tabs;
}())
, _currentTabType(full()
? session().settings().selectorTab()
: SelectorTab::Emoji) {
: mediaEditor()
? SelectorTab::Stickers
: SelectorTab::Emoji)
, _hasEmojiTab(ranges::contains(_tabs, SelectorTab::Emoji, &Tab::type))
, _hasStickersTab(ranges::contains(_tabs, SelectorTab::Stickers, &Tab::type))
, _hasGifsTab(ranges::contains(_tabs, SelectorTab::Gifs, &Tab::type))
, _hasMasksTab(ranges::contains(_tabs, SelectorTab::Masks, &Tab::type))
, _tabbed(_tabs.size() > 1) {
resize(st::emojiPanWidth, st::emojiPanMaxHeight);
for (auto &tab : _tabs) {
if (!tab.widget()) {
continue;
}
tab.footer()->hide();
tab.widget()->hide();
}
createTabsSlider();
if (tabbed()) {
createTabsSlider();
}
setWidgetToScrollArea();
_bottomShadow->setGeometry(0, _scroll->y() + _scroll->height() - st::lineWidth, width(), st::lineWidth);
_bottomShadow->setGeometry(
0,
_scroll->y() + _scroll->height() - st::lineWidth,
width(),
st::lineWidth);
for (auto &tab : _tabs) {
const auto widget = tab.widget();
if (!widget) {
continue;
}
widget->scrollToRequests(
) | rpl::start_with_next([=, tab = &tab](int y) {
@@ -338,7 +361,7 @@ TabbedSelector::TabbedSelector(
}
rpl::merge(
(full()
(hasStickersTab()
? stickers()->scrollUpdated() | rpl::map_to(0)
: rpl::never<int>() | rpl::type_erased()),
_scroll->scrollTopChanges()
@@ -346,13 +369,15 @@ TabbedSelector::TabbedSelector(
handleScroll();
}, lifetime());
if (full()) {
if (_topShadow) {
_topShadow->raise();
}
_bottomShadow->raise();
if (full()) {
if (_tabsSlider) {
_tabsSlider->raise();
}
if (hasStickersTab() || hasGifsTab()) {
session().changes().peerUpdates(
Data::PeerUpdate::Flag::Rights
) | rpl::filter([=](const Data::PeerUpdate &update) {
@@ -360,11 +385,12 @@ TabbedSelector::TabbedSelector(
}) | rpl::start_with_next([=] {
checkRestrictedPeer();
}, lifetime());
}
session().api().stickerSetInstalled(
) | rpl::start_with_next([this](uint64 setId) {
_tabsSlider->setActiveSection(
static_cast<int>(SelectorTab::Stickers));
if (hasStickersTab()) {
session().data().stickers().stickerSetInstalled(
) | rpl::start_with_next([=](uint64 setId) {
_tabsSlider->setActiveSection(indexByType(SelectorTab::Stickers));
stickers()->showStickerSet(setId);
_showRequests.fire({});
}, lifetime());
@@ -387,11 +413,8 @@ Main::Session &TabbedSelector::session() const {
return _controller->session();
}
TabbedSelector::Tab TabbedSelector::createTab(SelectorTab type) {
TabbedSelector::Tab TabbedSelector::createTab(SelectorTab type, int index) {
auto createWidget = [&]() -> object_ptr<Inner> {
if (!full() && type != SelectorTab::Emoji) {
return { nullptr };
}
switch (type) {
case SelectorTab::Emoji:
return object_ptr<EmojiListWidget>(this, _controller);
@@ -399,52 +422,94 @@ TabbedSelector::Tab TabbedSelector::createTab(SelectorTab type) {
return object_ptr<StickersListWidget>(this, _controller);
case SelectorTab::Gifs:
return object_ptr<GifsListWidget>(this, _controller);
case SelectorTab::Masks:
return object_ptr<StickersListWidget>(this, _controller, true);
}
Unexpected("Type in TabbedSelector::createTab.");
};
return Tab{ type, createWidget() };
return Tab{ type, index, createWidget() };
}
bool TabbedSelector::full() const {
return (_mode == Mode::Full);
}
bool TabbedSelector::mediaEditor() const {
return (_mode == Mode::MediaEditor);
}
bool TabbedSelector::tabbed() const {
return _tabbed;
}
bool TabbedSelector::hasEmojiTab() const {
return _hasEmojiTab;
}
bool TabbedSelector::hasStickersTab() const {
return _hasStickersTab;
}
bool TabbedSelector::hasGifsTab() const {
return _hasGifsTab;
}
bool TabbedSelector::hasMasksTab() const {
return _hasMasksTab;
}
rpl::producer<EmojiPtr> TabbedSelector::emojiChosen() const {
return emoji()->chosen();
}
rpl::producer<TabbedSelector::FileChosen> TabbedSelector::fileChosen() const {
return full()
? rpl::merge(stickers()->chosen(), gifs()->fileChosen())
: rpl::never<TabbedSelector::FileChosen>() | rpl::type_erased();
auto never = rpl::never<TabbedSelector::FileChosen>(
) | rpl::type_erased();
return rpl::merge(
hasStickersTab() ? stickers()->chosen() : never,
hasGifsTab() ? gifs()->fileChosen() : never,
hasMasksTab() ? masks()->chosen() : never);
}
auto TabbedSelector::photoChosen() const
-> rpl::producer<TabbedSelector::PhotoChosen>{
return full() ? gifs()->photoChosen() : nullptr;
return hasGifsTab() ? gifs()->photoChosen() : nullptr;
}
auto TabbedSelector::inlineResultChosen() const
-> rpl::producer<InlineChosen> {
return full() ? gifs()->inlineResultChosen() : nullptr;
return hasGifsTab() ? gifs()->inlineResultChosen() : nullptr;
}
rpl::producer<> TabbedSelector::cancelled() const {
return full() ? gifs()->cancelRequests() : nullptr;
return hasGifsTab() ? gifs()->cancelRequests() : nullptr;
}
rpl::producer<> TabbedSelector::checkForHide() const {
return full() ? stickers()->checkForHide() : nullptr;
auto never = rpl::never<>();
return rpl::merge(
hasStickersTab() ? stickers()->checkForHide() : never,
hasMasksTab() ? masks()->checkForHide() : never);
}
rpl::producer<> TabbedSelector::slideFinished() const {
return _slideFinished.events();
}
void TabbedSelector::updateTabsSliderGeometry() {
if (!_tabsSlider) {
return;
}
const auto w = mediaEditor() && hasMasksTab() && masks()->mySetsEmpty()
? width() / 2
: width();
_tabsSlider->resizeToWidth(w);
_tabsSlider->moveToLeft(0, 0);
}
void TabbedSelector::resizeEvent(QResizeEvent *e) {
if (full()) {
_tabsSlider->resizeToWidth(width());
_tabsSlider->moveToLeft(0, 0);
updateTabsSliderGeometry();
if (_topShadow && _tabsSlider) {
_topShadow->setGeometry(
_tabsSlider->x(),
_tabsSlider->bottomNoMargins() - st::lineWidth,
@@ -476,14 +541,15 @@ void TabbedSelector::resizeEvent(QResizeEvent *e) {
updateInnerGeometry();
updateScrollGeometry();
}
_bottomShadow->setGeometry(0, _scroll->y() + _scroll->height() - st::lineWidth, width(), st::lineWidth);
_bottomShadow->setGeometry(
0,
_scroll->y() + _scroll->height() - st::lineWidth,
width(),
st::lineWidth);
updateRestrictedLabelGeometry();
_footerTop = height() - st::emojiFooterHeight;
for (auto &tab : _tabs) {
if (!tab.widget()) {
continue;
}
tab.footer()->resizeToWidth(width());
tab.footer()->moveToLeft(0, _footerTop);
}
@@ -521,14 +587,22 @@ void TabbedSelector::paintEvent(QPaintEvent *e) {
void TabbedSelector::paintSlideFrame(Painter &p) {
if (_roundRadius > 0) {
if (full()) {
auto topPart = QRect(0, 0, width(), _tabsSlider->height() + _roundRadius);
Ui::FillRoundRect(p, topPart, st::emojiPanBg, ImageRoundRadius::Small, RectPart::FullTop | RectPart::NoTopBottom);
} else {
auto topPart = QRect(0, 0, width(), 3 * _roundRadius);
Ui::FillRoundRect(p, topPart, st::emojiPanBg, ImageRoundRadius::Small, RectPart::FullTop);
}
} else if (full()) {
const auto topPart = QRect(
0,
0,
width(),
_tabsSlider
? _tabsSlider->height() + _roundRadius
: 3 * _roundRadius);
Ui::FillRoundRect(
p,
topPart,
st::emojiPanBg,
ImageRoundRadius::Small,
tabbed()
? RectPart::FullTop | RectPart::NoTopBottom
: RectPart::FullTop);
} else if (_tabsSlider) {
p.fillRect(0, 0, width(), _tabsSlider->height(), st::emojiPanBg);
}
auto slideDt = _a_slide.value(1.);
@@ -536,21 +610,39 @@ void TabbedSelector::paintSlideFrame(Painter &p) {
}
void TabbedSelector::paintContent(Painter &p) {
auto &bottomBg = hasSectionIcons() ? st::emojiPanCategories : st::emojiPanBg;
auto &bottomBg = hasSectionIcons()
? st::emojiPanCategories
: st::emojiPanBg;
if (_roundRadius > 0) {
if (full()) {
auto topPart = QRect(0, 0, width(), _tabsSlider->height() + _roundRadius);
Ui::FillRoundRect(p, topPart, st::emojiPanBg, ImageRoundRadius::Small, RectPart::FullTop | RectPart::NoTopBottom);
} else {
auto topPart = QRect(0, 0, width(), 3 * _roundRadius);
Ui::FillRoundRect(p, topPart, st::emojiPanBg, ImageRoundRadius::Small, RectPart::FullTop);
}
const auto topPart = QRect(
0,
0,
width(),
_tabsSlider
? _tabsSlider->height() + _roundRadius
: 3 * _roundRadius);
Ui::FillRoundRect(
p,
topPart,
st::emojiPanBg,
ImageRoundRadius::Small,
tabbed()
? RectPart::FullTop | RectPart::NoTopBottom
: RectPart::FullTop);
auto bottomPart = QRect(0, _footerTop - _roundRadius, width(), st::emojiFooterHeight + _roundRadius);
auto bottomParts = RectPart::NoTopBottom | RectPart::FullBottom;
Ui::FillRoundRect(p, bottomPart, bottomBg, ImageRoundRadius::Small, bottomParts);
const auto bottomPart = QRect(
0,
_footerTop - _roundRadius,
width(),
st::emojiFooterHeight + _roundRadius);
Ui::FillRoundRect(
p,
bottomPart,
bottomBg,
ImageRoundRadius::Small,
RectPart::NoTopBottom | RectPart::FullBottom);
} else {
if (full()) {
if (_tabsSlider) {
p.fillRect(0, 0, width(), _tabsSlider->height(), st::emojiPanBg);
}
p.fillRect(0, _footerTop, width(), st::emojiFooterHeight, bottomBg);
@@ -561,17 +653,27 @@ void TabbedSelector::paintContent(Painter &p) {
if (_restrictedLabel) {
p.fillRect(0, sidesTop, width(), sidesHeight, st::emojiPanBg);
} else {
p.fillRect(myrtlrect(width() - st::emojiScroll.width, sidesTop, st::emojiScroll.width, sidesHeight), st::emojiPanBg);
p.fillRect(myrtlrect(0, sidesTop, st::roundRadiusSmall, sidesHeight), st::emojiPanBg);
p.fillRect(
myrtlrect(
width() - st::emojiScroll.width,
sidesTop,
st::emojiScroll.width,
sidesHeight),
st::emojiPanBg);
p.fillRect(
myrtlrect(0, sidesTop, st::roundRadiusSmall, sidesHeight),
st::emojiPanBg);
}
}
int TabbedSelector::marginTop() const {
return full() ? (_tabsSlider->height() - st::lineWidth) : _roundRadius;
return _tabsSlider
? (_tabsSlider->height() - st::lineWidth)
: _roundRadius;
}
int TabbedSelector::scrollTop() const {
return full() ? marginTop() : 0;
return tabbed() ? marginTop() : 0;
}
int TabbedSelector::marginBottom() const {
@@ -579,19 +681,31 @@ int TabbedSelector::marginBottom() const {
}
void TabbedSelector::refreshStickers() {
if (!full()) {
return;
if (hasStickersTab()) {
stickers()->refreshStickers();
if (isHidden() || _currentTabType != SelectorTab::Stickers) {
stickers()->preloadImages();
}
}
stickers()->refreshStickers();
if (isHidden() || _currentTabType != SelectorTab::Stickers) {
stickers()->preloadImages();
if (hasMasksTab()) {
const auto masksList = masks();
masksList->refreshStickers();
if (isHidden() || _currentTabType != SelectorTab::Masks) {
masksList->preloadImages();
}
fillTabsSliderSections();
updateTabsSliderGeometry();
if (hasStickersTab() && masksList->mySetsEmpty()) {
_tabsSlider->setActiveSection(indexByType(SelectorTab::Stickers));
}
}
}
bool TabbedSelector::preventAutoHide() const {
return full()
? (stickers()->preventAutoHide() || hasMenu())
: false;
return (hasStickersTab() ? stickers()->preventAutoHide() : false)
|| (hasMasksTab() ? masks()->preventAutoHide() : false)
|| hasMenu();
}
bool TabbedSelector::hasMenu() const {
@@ -603,13 +717,17 @@ QImage TabbedSelector::grabForAnimation() {
auto slideAnimation = base::take(_a_slide);
showAll();
if (full()) {
if (_topShadow) {
_topShadow->hide();
}
if (_tabsSlider) {
_tabsSlider->hide();
}
Ui::SendPendingMoveResizeEvents(this);
auto result = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
auto result = QImage(
size() * cIntRetinaFactor(),
QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(cRetinaFactor());
result.fill(Qt::transparent);
render(&result);
@@ -630,9 +748,6 @@ QRect TabbedSelector::floatPlayerAvailableRect() const {
void TabbedSelector::hideFinished() {
for (auto &tab : _tabs) {
if (!tab.widget()) {
continue;
}
tab.widget()->panelHideFinished();
}
_a_slide.stop();
@@ -640,9 +755,12 @@ void TabbedSelector::hideFinished() {
}
void TabbedSelector::showStarted() {
if (full()) {
if (hasStickersTab()) {
session().api().updateStickers();
}
if (hasMasksTab()) {
session().api().updateMasks();
}
currentTab()->widget()->refreshRecent();
currentTab()->widget()->preloadImages();
_a_slide.stop();
@@ -670,13 +788,14 @@ void TabbedSelector::afterShown() {
}
void TabbedSelector::setCurrentPeer(PeerData *peer) {
if (!full()) {
return;
if (hasGifsTab()) {
gifs()->setInlineQueryPeer(peer);
}
gifs()->setInlineQueryPeer(peer);
_currentPeer = peer;
checkRestrictedPeer();
stickers()->showMegagroupSet(peer ? peer->asMegagroup() : nullptr);
if (hasStickersTab()) {
stickers()->showMegagroupSet(peer ? peer->asMegagroup() : nullptr);
}
}
void TabbedSelector::checkRestrictedPeer() {
@@ -730,16 +849,20 @@ void TabbedSelector::showAll() {
_scroll->show();
_bottomShadow->setVisible(_currentTabType == SelectorTab::Gifs);
}
if (full()) {
if (_topShadow) {
_topShadow->show();
}
if (_tabsSlider) {
_tabsSlider->show();
}
}
void TabbedSelector::hideForSliding() {
hideChildren();
if (full()) {
if (_topShadow) {
_topShadow->show();
}
if (_tabsSlider) {
_tabsSlider->show();
}
currentTab()->widget()->clearSelection();
@@ -753,48 +876,70 @@ void TabbedSelector::handleScroll() {
void TabbedSelector::setRoundRadius(int radius) {
_roundRadius = radius;
if (full()) {
if (_tabsSlider) {
_tabsSlider->setRippleTopRoundRadius(_roundRadius);
}
}
void TabbedSelector::createTabsSlider() {
if (!full()) {
return;
}
_tabsSlider.create(this, st::emojiTabs);
auto sections = QStringList();
sections.push_back(tr::lng_switch_emoji(tr::now).toUpper());
sections.push_back(tr::lng_switch_stickers(tr::now).toUpper());
sections.push_back(tr::lng_switch_gifs(tr::now).toUpper());
_tabsSlider->setSections(sections);
fillTabsSliderSections();
_tabsSlider->setActiveSectionFast(static_cast<int>(_currentTabType));
_tabsSlider->setActiveSectionFast(indexByType(_currentTabType));
_tabsSlider->sectionActivated(
) | rpl::start_with_next([=] {
switchTab();
}, lifetime());
}
void TabbedSelector::fillTabsSliderSections() {
if (!_tabsSlider) {
return;
}
const auto sections = ranges::views::all(
_tabs
) | ranges::views::filter([&](const Tab &tab) {
return (tab.type() == SelectorTab::Masks)
? !masks()->mySetsEmpty()
: true;
}) | ranges::views::transform([&](const Tab &tab) {
return [&] {
switch (tab.type()) {
case SelectorTab::Emoji:
return tr::lng_switch_emoji;
case SelectorTab::Stickers:
return tr::lng_switch_stickers;
case SelectorTab::Gifs:
return tr::lng_switch_gifs;
case SelectorTab::Masks:
return tr::lng_switch_masks;
}
Unexpected("SelectorTab value in fillTabsSliderSections.");
}()(tr::now).toUpper();
}) | ranges::to_vector;
_tabsSlider->setSections(sections);
}
bool TabbedSelector::hasSectionIcons() const {
return (_currentTabType != SelectorTab::Gifs) && !_restrictedLabel;
}
void TabbedSelector::switchTab() {
Expects(full());
Expects(tabbed());
auto tab = _tabsSlider->activeSection();
Assert(tab >= 0 && tab < Tab::kCount);
auto newTabType = static_cast<SelectorTab>(tab);
const auto tab = _tabsSlider->activeSection();
Assert(tab >= 0 && tab < _tabs.size());
const auto newTabType = typeByIndex(tab);
if (_currentTabType == newTabType) {
_scroll->scrollToY(0);
return;
}
auto wasSectionIcons = hasSectionIcons();
auto wasTab = _currentTabType;
const auto wasSectionIcons = hasSectionIcons();
const auto wasTab = _currentTabType;
const auto wasIndex = indexByType(_currentTabType);
currentTab()->saveScrollTop();
beforeHiding();
@@ -817,21 +962,38 @@ void TabbedSelector::switchTab() {
auto nowCache = grabForAnimation();
auto direction = (wasTab > _currentTabType) ? SlideAnimation::Direction::LeftToRight : SlideAnimation::Direction::RightToLeft;
auto direction = (wasIndex > indexByType(_currentTabType))
? SlideAnimation::Direction::LeftToRight
: SlideAnimation::Direction::RightToLeft;
if (direction == SlideAnimation::Direction::LeftToRight) {
std::swap(wasCache, nowCache);
}
_slideAnimation = std::make_unique<SlideAnimation>();
auto slidingRect = QRect(0, _scroll->y() * cIntRetinaFactor(), width() * cIntRetinaFactor(), (height() - _scroll->y()) * cIntRetinaFactor());
_slideAnimation->setFinalImages(direction, std::move(wasCache), std::move(nowCache), slidingRect, wasSectionIcons);
_slideAnimation->setCornerMasks(Images::CornersMask(ImageRoundRadius::Small));
const auto slidingRect = QRect(
0,
_scroll->y() * cIntRetinaFactor(),
width() * cIntRetinaFactor(),
(height() - _scroll->y()) * cIntRetinaFactor());
_slideAnimation->setFinalImages(
direction,
std::move(wasCache),
std::move(nowCache),
slidingRect,
wasSectionIcons);
_slideAnimation->setCornerMasks(
Images::CornersMask(ImageRoundRadius::Small));
_slideAnimation->start();
hideForSliding();
getTab(wasTab)->widget()->hideFinished();
getTab(wasIndex)->widget()->hideFinished();
_a_slide.start([this] { update(); }, 0., 1., st::emojiPanSlideDuration, anim::linear);
_a_slide.start(
[=] { update(); },
0.,
1.,
st::emojiPanSlideDuration,
anim::linear);
update();
if (full()) {
@@ -841,19 +1003,31 @@ void TabbedSelector::switchTab() {
}
not_null<EmojiListWidget*> TabbedSelector::emoji() const {
return static_cast<EmojiListWidget*>(getTab(SelectorTab::Emoji)->widget());
Expects(hasEmojiTab());
return static_cast<EmojiListWidget*>(
getTab(indexByType(SelectorTab::Emoji))->widget());
}
not_null<StickersListWidget*> TabbedSelector::stickers() const {
Expects(full());
Expects(hasStickersTab());
return static_cast<StickersListWidget*>(getTab(SelectorTab::Stickers)->widget());
return static_cast<StickersListWidget*>(
getTab(indexByType(SelectorTab::Stickers))->widget());
}
not_null<GifsListWidget*> TabbedSelector::gifs() const {
Expects(full());
Expects(hasGifsTab());
return static_cast<GifsListWidget*>(getTab(SelectorTab::Gifs)->widget());
return static_cast<GifsListWidget*>(
getTab(indexByType(SelectorTab::Gifs))->widget());
}
not_null<StickersListWidget*> TabbedSelector::masks() const {
Expects(hasMasksTab());
return static_cast<StickersListWidget*>(
getTab(indexByType(SelectorTab::Masks))->widget());
}
void TabbedSelector::setWidgetToScrollArea() {
@@ -873,7 +1047,7 @@ void TabbedSelector::scrollToY(int y) {
_scroll->scrollToY(y);
// Qt render glitch workaround, shadow sometimes disappears if we just scroll to y.
if (full()) {
if (_topShadow) {
_topShadow->update();
}
}
@@ -894,6 +1068,40 @@ rpl::producer<> TabbedSelector::contextMenuRequested() const {
}) | rpl::to_empty;
}
SelectorTab TabbedSelector::typeByIndex(int index) const {
for (const auto &tab : _tabs) {
if (tab.index() == index) {
return tab.type();
}
}
Unexpected("Type in TabbedSelector::typeByIndex.");
}
int TabbedSelector::indexByType(SelectorTab type) const {
for (const auto &tab : _tabs) {
if (tab.type() == type) {
return tab.index();
}
}
Unexpected("Index in TabbedSelector::indexByType.");
}
not_null<TabbedSelector::Tab*> TabbedSelector::getTab(int index) {
return &(_tabs[index]);
}
not_null<const TabbedSelector::Tab*> TabbedSelector::getTab(int index) const {
return &_tabs[index];
}
not_null<TabbedSelector::Tab*> TabbedSelector::currentTab() {
return &_tabs[indexByType(_currentTabType)];
}
not_null<const TabbedSelector::Tab*> TabbedSelector::currentTab() const {
return &_tabs[indexByType(_currentTabType)];
}
TabbedSelector::Inner::Inner(
QWidget *parent,
not_null<Window::SessionController*> controller)
@@ -917,7 +1125,9 @@ void TabbedSelector::Inner::disableScroll(bool disabled) {
_disableScrollRequests.fire_copy(disabled);
}
void TabbedSelector::Inner::visibleTopBottomUpdated(int visibleTop, int visibleBottom) {
void TabbedSelector::Inner::visibleTopBottomUpdated(
int visibleTop,
int visibleBottom) {
_visibleTop = visibleTop;
_visibleBottom = visibleBottom;
}

View File

@@ -44,6 +44,7 @@ enum class SelectorTab {
Emoji,
Stickers,
Gifs,
Masks,
};
class EmojiListWidget;
@@ -63,7 +64,8 @@ public:
using InlineChosen = InlineBots::ResultSelected;
enum class Mode {
Full,
EmojiOnly
EmojiOnly,
MediaEditor,
};
TabbedSelector(
@@ -130,9 +132,7 @@ protected:
private:
class Tab {
public:
static constexpr auto kCount = 3;
Tab(SelectorTab type, object_ptr<Inner> widget);
Tab(SelectorTab type, int index, object_ptr<Inner> widget);
object_ptr<Inner> takeWidget();
void returnWidget(object_ptr<Inner> widget);
@@ -140,6 +140,9 @@ private:
SelectorTab type() const {
return _type;
}
int index() const {
return _index;
}
Inner *widget() const {
return _weak;
}
@@ -156,7 +159,8 @@ private:
}
private:
SelectorTab _type = SelectorTab::Emoji;
const SelectorTab _type;
const int _index;
object_ptr<Inner> _widget = { nullptr };
QPointer<Inner> _weak;
object_ptr<InnerFooter> _footer;
@@ -165,7 +169,13 @@ private:
};
bool full() const;
Tab createTab(SelectorTab type);
bool mediaEditor() const;
bool tabbed() const;
bool hasEmojiTab() const;
bool hasStickersTab() const;
bool hasGifsTab() const;
bool hasMasksTab() const;
Tab createTab(SelectorTab type, int index);
void paintSlideFrame(Painter &p);
void paintContent(Painter &p);
@@ -182,25 +192,25 @@ private:
void showAll();
void hideForSliding();
SelectorTab typeByIndex(int index) const;
int indexByType(SelectorTab type) const;
bool hasSectionIcons() const;
void setWidgetToScrollArea();
void createTabsSlider();
void fillTabsSliderSections();
void updateTabsSliderGeometry();
void switchTab();
not_null<Tab*> getTab(SelectorTab type) {
return &_tabs[static_cast<int>(type)];
}
not_null<const Tab*> getTab(SelectorTab type) const {
return &_tabs[static_cast<int>(type)];
}
not_null<Tab*> currentTab() {
return getTab(_currentTabType);
}
not_null<const Tab*> currentTab() const {
return getTab(_currentTabType);
}
not_null<Tab*> getTab(int index);
not_null<const Tab*> getTab(int index) const;
not_null<Tab*> currentTab();
not_null<const Tab*> currentTab() const;
not_null<EmojiListWidget*> emoji() const;
not_null<StickersListWidget*> stickers() const;
not_null<GifsListWidget*> gifs() const;
not_null<StickersListWidget*> masks() const;
const not_null<Window::SessionController*> _controller;
@@ -218,9 +228,15 @@ private:
object_ptr<Ui::PlainShadow> _bottomShadow;
object_ptr<Ui::ScrollArea> _scroll;
object_ptr<Ui::FlatLabel> _restrictedLabel = { nullptr };
std::array<Tab, Tab::kCount> _tabs;
std::vector<Tab> _tabs;
SelectorTab _currentTabType = SelectorTab::Emoji;
const bool _hasEmojiTab;
const bool _hasStickersTab;
const bool _hasGifsTab;
const bool _hasMasksTab;
const bool _tabbed;
base::unique_qptr<Ui::PopupMenu> _menu;
Fn<void(SelectorTab)> _afterShownCallback;

View File

@@ -58,6 +58,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_lock_widgets.h"
#include "history/history_location_manager.h"
#include "ui/widgets/tooltip.h"
#include "ui/gl/gl_detection.h"
#include "ui/image/image.h"
#include "ui/text/text_options.h"
#include "ui/emoji_config.h"
@@ -281,8 +282,7 @@ void Application::run() {
LOG(("Shortcuts Error: %1").arg(error));
}
if (!Platform::IsMac()
&& Ui::Integration::Instance().openglLastCheckFailed()) {
if (!Platform::IsMac() && Ui::GL::LastCrashCheckFailed()) {
showOpenGLCrashNotification();
}
@@ -297,14 +297,14 @@ void Application::run() {
void Application::showOpenGLCrashNotification() {
const auto enable = [=] {
Ui::GL::ForceDisable(false);
Ui::Integration::Instance().openglCheckFinish();
Ui::GL::CrashCheckFinish();
Core::App().settings().setDisableOpenGL(false);
Local::writeSettings();
App::restart();
};
const auto keepDisabled = [=] {
Ui::GL::ForceDisable(true);
Ui::Integration::Instance().openglCheckFinish();
Ui::GL::CrashCheckFinish();
Core::App().settings().setDisableOpenGL(true);
Local::writeSettings();
};
@@ -849,7 +849,7 @@ bool Application::passcodeLocked() const {
void Application::updateNonIdle() {
_lastNonIdleTime = crl::now();
if (const auto session = maybeActiveSession()) {
session->updates().checkIdleFinish();
session->updates().checkIdleFinish(_lastNonIdleTime);
}
}
@@ -876,19 +876,21 @@ bool Application::someSessionExists() const {
return false;
}
void Application::checkAutoLock() {
void Application::checkAutoLock(crl::time lastNonIdleTime) {
if (!_domain->local().hasLocalPasscode()
|| passcodeLocked()
|| !someSessionExists()) {
_shouldLockAt = 0;
_autoLockTimer.cancel();
return;
} else if (!lastNonIdleTime) {
lastNonIdleTime = this->lastNonIdleTime();
}
checkLocalTime();
const auto now = crl::now();
const auto shouldLockInMs = _settings.autoLock() * 1000LL;
const auto checkTimeMs = now - lastNonIdleTime();
const auto checkTimeMs = now - lastNonIdleTime;
if (checkTimeMs >= shouldLockInMs || (_shouldLockAt > 0 && now > _shouldLockAt + kAutoLockTimeoutLateMs)) {
_shouldLockAt = 0;
_autoLockTimer.cancel();
@@ -910,7 +912,7 @@ void Application::checkAutoLockIn(crl::time time) {
void Application::localPasscodeChanged() {
_shouldLockAt = 0;
_autoLockTimer.cancel();
checkAutoLock();
checkAutoLock(crl::now());
}
bool Application::hasActiveWindow(not_null<Main::Session*> session) const {

View File

@@ -245,7 +245,7 @@ public:
rpl::producer<bool> passcodeLockChanges() const;
rpl::producer<bool> passcodeLockValue() const;
void checkAutoLock();
void checkAutoLock(crl::time lastNonIdleTime = 0);
void checkAutoLockIn(crl::time time);
void localPasscodeChanged();

View File

@@ -127,6 +127,25 @@ std::map<int, const char*> BetaLogs() {
"- Several bug and crash fixes.\n"
},
{
2007010,
"- Added ability to mix together bold, italic and other formatting.\n"
"- Fix voice chats and video calls OpenGL with some drivers on Windows.\n"
"- Several bug fixes.\n"
},
{
2008006,
"- Added a simple image editor. "
"Crop photos or highlight parts of screenshots before sending.\n"
"- Use Direct3D 9 backend in ANGLE by default (Windows).\n"
"- Fix \"Show in Finder\" not focusing the Finder window (macOS).\n"
"- Use GTK from a child process (Linux).\n"
},
};
};

View File

@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/platform/base_platform_info.h"
#include "webrtc/webrtc_create_adm.h"
#include "ui/gl/gl_detection.h"
#include "calls/group/calls_group_common.h"
#include "facades.h"
namespace Core {
@@ -94,23 +95,36 @@ QByteArray Settings::serialize() const {
+ sizeof(qint32) * 5
+ Serialize::stringSize(_downloadPath.current())
+ Serialize::bytearraySize(_downloadPathBookmark)
+ sizeof(qint32) * 12
+ sizeof(qint32) * 9
+ Serialize::stringSize(_callOutputDeviceId)
+ Serialize::stringSize(_callInputDeviceId)
+ Serialize::stringSize(_callVideoInputDeviceId)
+ sizeof(qint32) * 5
+ Serialize::bytearraySize(proxy);
+ sizeof(qint32) * 5;
for (const auto &[key, value] : _soundOverrides) {
size += Serialize::stringSize(key) + Serialize::stringSize(value);
}
size += sizeof(qint32) * 13
+ Serialize::bytearraySize(_videoPipGeometry)
+ sizeof(qint32)
+ (_dictionariesEnabled.current().size() * sizeof(quint64))
+ sizeof(qint32) * 12
+ Serialize::stringSize(_callVideoInputDeviceId)
+ sizeof(qint32) * 2
+ Serialize::bytearraySize(_groupCallPushToTalkShortcut)
+ sizeof(qint64)
+ sizeof(qint32) * 2
+ Serialize::bytearraySize(windowPosition)
+ sizeof(qint32)
+ Serialize::bytearraySize(_photoEditorBrush);
for (const auto &[id, rating] : recentEmojiPreloadData) {
size += Serialize::stringSize(id) + sizeof(quint16);
}
size += sizeof(qint32);
for (const auto &[id, variant] : _emojiVariants) {
size += Serialize::stringSize(id) + sizeof(quint8);
}
size += Serialize::bytearraySize(_videoPipGeometry);
size += Serialize::bytearraySize(windowPosition);
size += sizeof(qint32) * 3
+ Serialize::bytearraySize(proxy)
+ sizeof(qint32) * 2;
auto result = QByteArray();
result.reserve(size);
@@ -198,10 +212,13 @@ QByteArray Settings::serialize() const {
stream << id << quint8(variant);
}
stream
<< qint32(_disableOpenGL ? 1 : 0)
<< qint32(0) // Old Disable OpenGL
<< qint32(_groupCallNoiseSuppression ? 1 : 0)
<< _workMode.current()
<< proxy;
<< qint32(_workMode.current())
<< proxy
<< qint32(_hiddenGroupCallTooltips.value())
<< qint32(_disableOpenGL ? 1 : 0)
<< _photoEditorBrush;
}
return result;
}
@@ -281,6 +298,8 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
qint32 groupCallNoiseSuppression = _groupCallNoiseSuppression ? 1 : 0;
qint32 workMode = static_cast<qint32>(_workMode.current());
QByteArray proxy;
qint32 hiddenGroupCallTooltips = qint32(_hiddenGroupCallTooltips.value());
QByteArray photoEditorBrush = _photoEditorBrush;
stream >> themesAccentColors;
if (!stream.atEnd()) {
@@ -410,7 +429,8 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
}
}
if (!stream.atEnd()) {
stream >> disableOpenGL;
qint32 disableOpenGLOld;
stream >> disableOpenGLOld;
}
if (!stream.atEnd()) {
stream >> groupCallNoiseSuppression;
@@ -421,6 +441,15 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
if (!stream.atEnd()) {
stream >> proxy;
}
if (!stream.atEnd()) {
stream >> hiddenGroupCallTooltips;
}
if (!stream.atEnd()) {
stream >> disableOpenGL;
}
if (!stream.atEnd()) {
stream >> photoEditorBrush;
}
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for Core::Settings::constructFromSerialized()"));
@@ -531,7 +560,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
_disableOpenGL = (disableOpenGL == 1);
if (!Platform::IsMac()) {
Ui::GL::ForceDisable(_disableOpenGL
|| Ui::Integration::Instance().openglLastCheckFailed());
|| Ui::GL::LastCrashCheckFailed());
}
_groupCallNoiseSuppression = (groupCallNoiseSuppression == 1);
const auto uncheckedWorkMode = static_cast<WorkMode>(workMode);
@@ -540,6 +569,17 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
case WorkMode::TrayOnly:
case WorkMode::WindowOnly: _workMode = uncheckedWorkMode; break;
}
_hiddenGroupCallTooltips = [&] {
using Tooltip = Calls::Group::StickedTooltip;
return Tooltip(0)
| ((hiddenGroupCallTooltips & int(Tooltip::Camera))
? Tooltip::Camera
: Tooltip(0))
| ((hiddenGroupCallTooltips & int(Tooltip::Microphone))
? Tooltip::Microphone
: Tooltip(0));
}();
_photoEditorBrush = photoEditorBrush;
}
QString Settings::getSoundPath(const QString &key) const {
@@ -795,6 +835,7 @@ void Settings::resetOnLastLogout() {
_notifyFromAll = true;
_tabbedReplacedWithInfo = false; // per-window
_systemDarkModeEnabled = false;
_hiddenGroupCallTooltips = 0;
_recentEmojiPreload.clear();
_recentEmoji.clear();

View File

@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/themes/window_themes_embedded.h"
#include "ui/chat/attach/attach_send_files_way.h"
#include "platform/platform_notifications_manager.h"
#include "base/flags.h"
#include "emoji.h"
enum class RectPart;
@@ -27,6 +28,10 @@ namespace Webrtc {
enum class Backend;
} // namespace Webrtc
namespace Calls::Group {
enum class StickedTooltip;
} // namespace Calls::Group
namespace Core {
struct WindowPosition {
@@ -90,6 +95,9 @@ public:
[[nodiscard]] rpl::producer<bool> adaptiveForWideValue() const {
return _adaptiveForWide.value();
}
[[nodiscard]] rpl::producer<bool> adaptiveForWideChanges() const {
return _adaptiveForWide.changes();
}
void setAdaptiveForWide(bool value) {
_adaptiveForWide = value;
}
@@ -426,6 +434,13 @@ public:
_videoPipGeometry = geometry;
}
[[nodiscard]] QByteArray photoEditorBrush() const {
return _photoEditorBrush;
}
void setPhotoEditorBrush(QByteArray brush) {
_photoEditorBrush = brush;
}
[[nodiscard]] float64 rememberedSongVolume() const {
return _rememberedSongVolume;
}
@@ -574,6 +589,13 @@ public:
_disableOpenGL = value;
}
[[nodiscard]] base::flags<Calls::Group::StickedTooltip> hiddenGroupCallTooltips() const {
return _hiddenGroupCallTooltips;
}
void setHiddenGroupCallTooltip(Calls::Group::StickedTooltip value) {
_hiddenGroupCallTooltips |= value;
}
[[nodiscard]] static bool ThirdColumnByDefault();
[[nodiscard]] static float64 DefaultDialogsWidthRatio();
[[nodiscard]] static qint32 SerializePlaybackSpeed(float64 speed) {
@@ -671,6 +693,7 @@ private:
WindowPosition _windowPosition; // per-window
bool _disableOpenGL = false;
rpl::variable<WorkMode> _workMode = WorkMode::WindowAndTray;
base::flags<Calls::Group::StickedTooltip> _hiddenGroupCallTooltips;
bool _tabbedReplacedWithInfo = false; // per-window
rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window
@@ -680,6 +703,8 @@ private:
bool _rememberedSoundNotifyFromTray = false;
bool _rememberedFlashBounceNotifyFromTray = false;
QByteArray _photoEditorBrush;
};
} // namespace Core

View File

@@ -12,7 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/platform/base_platform_info.h"
#include "base/platform/base_platform_file_utilities.h"
#include "ui/main_queue_processor.h"
#include "ui/ui_utility.h"
#include "core/crash_reports.h"
#include "core/update_checker.h"
#include "core/sandbox.h"
@@ -334,7 +333,6 @@ int Launcher::exec() {
// Must be started before Sandbox is created.
Platform::start();
Ui::DisableCustomScaling();
auto result = executeApplication();

View File

@@ -19,7 +19,7 @@ public:
static std::unique_ptr<Launcher> Create(int argc, char *argv[]);
int exec();
virtual int exec();
QString argumentsString() const;
bool customWorkingDir() const;

View File

@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/qthelp_url.h"
#include "base/qthelp_regex.h"
#include "base/qt_adapters.h"
#include "ui/ui_utility.h"
#include "ui/effects/animations.h"
#include "app.h"
@@ -312,6 +313,7 @@ void Sandbox::singleInstanceChecked() {
Logs::multipleInstances();
}
Ui::DisableCustomScaling();
refreshGlobalProxy();
if (!Logs::started() || (!cManyInstance() && !Logs::instanceChecked())) {
new NotStartedWindow();

Some files were not shown because too many files have changed in this diff Show More