From e4e5142a960ad9cdd2d72073d0b21e42826b4762 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Tue, 10 Mar 2026 17:50:37 +0100 Subject: [PATCH] Various improvements --- .vscode/launch.json | 19 -- .vscode/tasks.json | 110 ----------- .../Sources/Node/ChatListItemStrings.swift | 10 + .../Items/UniversalVideoGalleryItem.swift | 2 +- .../Postbox/Sources/MessageHistoryView.swift | 4 + .../Sources/ChatFloatingTopicsPanel.swift | 34 +++- .../Sources/ChatSideTopicsPanel.swift | 44 +++-- .../ContextControllerActionsStackNode.swift | 5 +- .../Sources/LegacyGlassView.swift | 48 +++-- .../HeaderPanelContainerComponent.swift | 2 +- .../MeshTransform/Sources/GenerateMesh.swift | 182 +++++------------- .../SidebarBottomIcon.imageset/Contents.json | 12 ++ .../SidebarBottomIcon.imageset/tabsdown.pdf | Bin 0 -> 6924 bytes .../SidebarIcon.imageset/Contents.json | 2 +- .../SidebarIcon.imageset/list_24.pdf | Bin 5235 -> 0 bytes .../SidebarIcon.imageset/tabsleft.pdf | Bin 0 -> 6968 bytes .../SidebarTopIcon.imageset/Contents.json | 12 ++ .../SidebarTopIcon.imageset/tabsup.pdf | Bin 0 -> 6933 bytes .../Sources/ChatControllerNode.swift | 10 +- .../Sources/ChatHistoryViewForLocation.swift | 3 + .../ChatInterfaceTitlePanelNodes.swift | 9 +- 21 files changed, 176 insertions(+), 332 deletions(-) delete mode 100644 .vscode/launch.json delete mode 100644 .vscode/tasks.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SidebarBottomIcon.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SidebarBottomIcon.imageset/tabsdown.pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SidebarIcon.imageset/list_24.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SidebarIcon.imageset/tabsleft.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SidebarTopIcon.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SidebarTopIcon.imageset/tabsup.pdf diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index e73dc82d35..0000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "configurations": [ - { - "name": "Debug Telegram", - "type": "lldb-dap", - "request": "attach", - "preLaunchTask": "_launch_telegram", - "debuggerRoot": "${workspaceFolder}", - "attachCommands": [ - "command script import '${workspaceFolder}/scripts/lldb_attach.py'" - ], - "terminateCommands": [ - "command script import '${workspaceFolder}/scripts/lldb_kill_app.py'" - ], - "internalConsoleOptions": "openOnSessionStart", - "timeout": 9999 - } - ] -} diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 6a356b8eb9..0000000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,110 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "Select Simulator for Apple Development", - "type": "shell", - "command": "./scripts/select_simulator.sh", - "presentation": { - "reveal": "always", - "focus": true, - "panel": "dedicated" - }, - "problemMatcher": [] - }, - { - "label": "Build Telegram", - "type": "shell", - "command": "./scripts/lldb_build.sh", - "options": { - "env": { - "BAZEL_LABEL_TO_RUN": "//Telegram:Telegram", - "BAZEL_EXTRA_BUILD_FLAGS": "" - } - }, - "group": { - "kind": "build" - }, - "problemMatcher": [ - { - "owner": "bazel", - "source": "bazel", - "fileLocation": [ - "relative", - "${workspaceFolder}" - ], - "pattern": { - "regexp": "^(.+?):(\\d+):(\\d+):\\s+(error|warning|note):\\s+(.*)$", - "file": 1, - "line": 2, - "column": 3, - "severity": 4, - "message": 5 - } - } - ] - }, - { - "label": "_launch_telegram", - "type": "shell", - "command": "./scripts/lldb_launch_and_debug.sh", - "options": { - "env": { - "BAZEL_LABEL_TO_RUN": "//Telegram:Telegram", - "BAZEL_EXTRA_BUILD_FLAGS": "", - "BAZEL_LAUNCH_ARGS": "" - } - }, - "presentation": { - "reveal": "always" - }, - "hide": true, - "isBackground": true, - "problemMatcher": [ - { - "owner": "bazel", - "source": "bazel", - "fileLocation": [ - "relative", - "${workspaceFolder}" - ], - "pattern": { - "regexp": "launcher_error in (.*): (.*)", - "kind": "file", - "file": 1, - "message": 2 - }, - "background": { - "activeOnStart": true, - "beginsPattern": "^Starting launch task\\.\\.\\.$", - "endsPattern": "^ph.telegra.Telegraph: .*" - } - }, - { - "owner": "bazel", - "source": "bazel", - "fileLocation": [ - "relative", - "${workspaceFolder}" - ], - "pattern": { - "regexp": "^(.+?):(\\d+):(\\d+):\\s+(error|warning|note):\\s+(.*)$", - "file": 1, - "line": 2, - "column": 3, - "severity": 4, - "message": 5 - }, - "background": { - "activeOnStart": true, - "beginsPattern": "^Starting launch task\\.\\.\\.$", - "endsPattern": "^ph.telegra.Telegraph: .*" - } - } - ], - "runOptions": { - "instanceLimit": 1 - } - } - ] -} diff --git a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift index 345d51078e..5ef43faa95 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift @@ -106,6 +106,16 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: if !message.text.isEmpty { messageText = message.text messageEntities = message._asMessage().textEntitiesAttribute?.entities ?? [] + for entity in messageEntities { + if case let .CustomEmoji(_, fileId) = entity.type { + if customEmojiRanges == nil { + customEmojiRanges = [] + } + let range = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound) + let attribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: message.associatedMedia[EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile) + customEmojiRanges?.append((range, attribute)) + } + } break } } diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index 3b880ea6ac..be576383cd 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -1215,7 +1215,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { playbackControlsIsSeekable = seekable playbackControlsIsPlaying = !paused } - if !self.areControlsVisible || hideControls { + if !self.controlsVisibility() || hideControls { playbackControlsIsVisible = false } diff --git a/submodules/Postbox/Sources/MessageHistoryView.swift b/submodules/Postbox/Sources/MessageHistoryView.swift index 3f62bc203e..68d06d679a 100644 --- a/submodules/Postbox/Sources/MessageHistoryView.swift +++ b/submodules/Postbox/Sources/MessageHistoryView.swift @@ -1295,6 +1295,10 @@ public final class MessageHistoryView: PostboxView { } init(_ mutableView: MutableMessageHistoryView) { + if case .external = mutableView.peerIds, mutableView.tag == nil, case .not = mutableView.namespaces { + assert(true) + } + self.tag = mutableView.tag self.namespaces = mutableView.namespaces self.isAddedToChatList = mutableView.isAddedToChatList diff --git a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatFloatingTopicsPanel.swift b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatFloatingTopicsPanel.swift index caf617d4ee..56133e1a15 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatFloatingTopicsPanel.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatFloatingTopicsPanel.swift @@ -180,7 +180,8 @@ public final class ChatFloatingTopicsPanel: Component { } } - if case .top = component.location { + switch component.location { + case .top, .bottom: let topPanel: ComponentView var topPanelTransition = transition if let current = self.topPanel { @@ -196,7 +197,7 @@ public final class ChatFloatingTopicsPanel: Component { context: component.context, theme: component.theme, strings: component.strings, - location: .top, + location: component.location == .top ? .top : .bottom, peerId: component.peerId, kind: component.kind, topicId: component.topicId, @@ -232,13 +233,15 @@ public final class ChatFloatingTopicsPanel: Component { } transition.setFrame(view: topPanelView, frame: topPanelFrame) } - } else if let topPanel = self.topPanel { - self.topPanel = nil - if let topPanelView = topPanel.view as? ChatSideTopicsPanel.View { - topPanelView.clipsToBounds = true - transition.setFrame(view: topPanelView, frame: CGRect(origin: CGPoint(x: 8.0, y: 0.0), size: CGSize(width: 16.0 + 72.0, height: topPanelView.bounds.height)), completion: { [weak topPanelView] _ in - topPanelView?.removeFromSuperview() - }) + default: + if let topPanel = self.topPanel { + self.topPanel = nil + if let topPanelView = topPanel.view as? ChatSideTopicsPanel.View { + topPanelView.clipsToBounds = true + transition.setFrame(view: topPanelView, frame: CGRect(origin: CGPoint(x: 8.0, y: 0.0), size: CGSize(width: 16.0 + 72.0, height: topPanelView.bounds.height)), completion: { [weak topPanelView] _ in + topPanelView?.removeFromSuperview() + }) + } } } @@ -291,11 +294,17 @@ public final class ChatFloatingTopicsPanel: Component { } public final class ChatTopicsHeaderPanelComponent: Component { + public enum Location { + case top + case bottom + } + public let context: AccountContext public let theme: PresentationTheme public let strings: PresentationStrings public let peerId: EnginePeer.Id public let kind: ChatSideTopicsPanel.Kind + public let location: Location public let topicId: Int64? public let controller: () -> ViewController? public let togglePanel: () -> Void @@ -308,6 +317,7 @@ public final class ChatTopicsHeaderPanelComponent: Component { strings: PresentationStrings, peerId: EnginePeer.Id, kind: ChatSideTopicsPanel.Kind, + location: Location, topicId: Int64?, controller: @escaping () -> ViewController?, togglePanel: @escaping () -> Void, @@ -319,6 +329,7 @@ public final class ChatTopicsHeaderPanelComponent: Component { self.strings = strings self.peerId = peerId self.kind = kind + self.location = location self.topicId = topicId self.controller = controller self.togglePanel = togglePanel @@ -342,6 +353,9 @@ public final class ChatTopicsHeaderPanelComponent: Component { if lhs.kind != rhs.kind { return false } + if lhs.location != rhs.location { + return false + } if lhs.topicId != rhs.topicId { return false } @@ -371,7 +385,7 @@ public final class ChatTopicsHeaderPanelComponent: Component { context: component.context, theme: component.theme, strings: component.strings, - location: .top, + location: component.location == .top ? .top : .bottom, peerId: component.peerId, kind: component.kind, topicId: component.topicId, diff --git a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift index 41cc0cedce..f5d15db9e7 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift @@ -44,6 +44,7 @@ public final class ChatSideTopicsPanel: Component { public enum Location { case side case top + case bottom } public enum Kind: Equatable { @@ -1105,11 +1106,20 @@ public final class ChatSideTopicsPanel: Component { } } + let normalIconName: String + switch location { + case .top: + normalIconName = "Chat/Title Panels/SidebarIcon" + case .side: + normalIconName = "Chat/Title Panels/SidebarBottomIcon" + case .bottom: + normalIconName = "Chat/Title Panels/SidebarTopIcon" + } let iconSize = self.icon.update( transition: .immediate, component: AnyComponent(BundleIconComponent( - name: isReordering ? "Media Editor/Done" : "Chat/Title Panels/SidebarIcon", - tintColor: location == .side ? theme.rootController.navigationBar.accentTextColor : theme.chat.inputPanel.panelControlColor, + name: isReordering ? "Media Editor/Done" : normalIconName, + tintColor: theme.chat.inputPanel.panelControlColor, maxSize: CGSize(width: 24.0, height: 24.0), scaleFactor: 1.0 )), @@ -1519,7 +1529,7 @@ public final class ChatSideTopicsPanel: Component { switch component.location { case .side: transition.setTransform(view: tabItemView, transform: CATransform3DMakeTranslation(-globalOffset, 0.0, 0.0)) - case .top: + case .top, .bottom: transition.setTransform(view: tabItemView, transform: CATransform3DMakeTranslation(0.0, -globalOffset, 0.0)) } } @@ -1560,7 +1570,7 @@ public final class ChatSideTopicsPanel: Component { switch component.location { case .side: selectedItemFrame = item.frame - case .top: + case .top, .bottom: selectedItemFrame = CGRect(origin: CGPoint(x: item.frame.minX + 5.0, y: item.frame.minY), size: CGSize(width: item.frame.width - 4.0 - 11.0, height: item.frame.height)) } } @@ -1568,7 +1578,7 @@ public final class ChatSideTopicsPanel: Component { switch component.location { case .side: beforePinnedItemsPosition = item.frame.maxY - case .top: + case .top, .bottom: beforePinnedItemsPosition = item.frame.maxX } } @@ -1597,7 +1607,7 @@ public final class ChatSideTopicsPanel: Component { switch component.location { case .side: beforePinnedItemsPosition = item.frame.maxY - case .top: + case .top, .bottom: beforePinnedItemsPosition = item.frame.maxX } } else { @@ -1605,7 +1615,7 @@ public final class ChatSideTopicsPanel: Component { switch component.location { case .side: afterPinnedItemsPosition = item.frame.minY - case .top: + case .top, .bottom: afterPinnedItemsPosition = item.frame.minX } } @@ -1622,7 +1632,7 @@ public final class ChatSideTopicsPanel: Component { switch component.location { case .side: afterPinnedItemsPosition = listView.bounds.height + 500.0 - case .top: + case .top, .bottom: afterPinnedItemsPosition = listView.bounds.width + 500.0 } } @@ -1638,7 +1648,7 @@ public final class ChatSideTopicsPanel: Component { switch component.location { case .side: selectedLineFrame = CGRect(origin: CGPoint(x: 0.0, y: selectedItemFrame.minY), size: CGSize(width: 4.0, height: selectedItemFrame.height)) - case .top: + case .top, .bottom: selectedLineFrame = CGRect(origin: CGPoint(x: selectedItemFrame.minX, y: listView.frame.maxY - 3.0), size: CGSize(width: selectedItemFrame.width, height: 3.0)) } @@ -1658,7 +1668,7 @@ public final class ChatSideTopicsPanel: Component { switch component.location { case .side: pinnedItemsBackgroundFrame = CGRect(origin: CGPoint(x: 5.0, y: beforePinnedItemsPosition), size: CGSize(width: listView.bounds.width - 5.0 - 4.0, height: afterPinnedItemsPosition - beforePinnedItemsPosition)) - case .top: + case .top, .bottom: pinnedItemsBackgroundFrame = CGRect(origin: CGPoint(x: beforePinnedItemsPosition, y: 4.0), size: CGSize(width: afterPinnedItemsPosition - beforePinnedItemsPosition, height: listView.bounds.height - 5.0 - 4.0)) } self.pinnedBackgroundContainer.updatePosition(position: pinnedItemsBackgroundFrame.origin, transition: pinnedItemsTransition) @@ -1754,7 +1764,7 @@ public final class ChatSideTopicsPanel: Component { image.draw(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: image.size.width)), blendMode: .destinationOut, alpha: 1.0) } })?.stretchableImage(withLeftCapWidth: Int(cornerRadius) + 1, topCapHeight: Int(cornerRadius) + 1) - case .top: + case .top, .bottom: self.scrollViewMask.image = generateImage(CGSize(width: 8.0 + 1.0, height: 1.0), rotatedContext: { size, context in UIGraphicsPushContext(context) defer { @@ -1785,7 +1795,7 @@ public final class ChatSideTopicsPanel: Component { context.setFillColor(component.theme.rootController.navigationBar.accentTextColor.cgColor) context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - size.height, y: 0.0), size: CGSize(width: size.height, height: size.height))) })?.stretchableImage(withLeftCapWidth: 1, topCapHeight: 4) - case .top: + case .top, .bottom: self.selectedLineView.image = generateImage(CGSize(width: 4.0, height: 3.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setFillColor(component.theme.rootController.navigationBar.accentTextColor.cgColor) @@ -1817,7 +1827,7 @@ public final class ChatSideTopicsPanel: Component { switch component.location { case .side: directionContainerInset = containerInsets.top - case .top: + case .top, .bottom: directionContainerInset = containerInsets.left } @@ -1876,7 +1886,7 @@ public final class ChatSideTopicsPanel: Component { case .side: itemFrame = CGRect(origin: CGPoint(x: 8.0 + 4.0, y: directionContainerInset + 6.0), size: itemSize) directionContainerInset += itemSize.height - case .top: + case .top, .bottom: itemFrame = CGRect(origin: CGPoint(x: 12.0, y: 6.0), size: itemSize) directionContainerInset += itemSize.width - 20.0 } @@ -1900,7 +1910,7 @@ public final class ChatSideTopicsPanel: Component { scrollSize = CGSize(width: availableSize.width, height: availableSize.height - directionContainerInset - environment.insets.top - containerInsets.bottom - additionalInsets.top - additionalInsets.bottom) scrollFrame = CGRect(origin: CGPoint(x: additionalInsets.left, y: directionContainerInset + environment.insets.top + additionalInsets.top), size: scrollSize) listContentInsets = UIEdgeInsets(top: 8.0, left: 0.0, bottom: 8.0, right: 0.0) - case .top: + case .top, .bottom: additionalInsets = UIEdgeInsets(top: 8.0, left: 8.0, bottom: 0.0, right: 8.0) scrollSize = CGSize(width: availableSize.width - additionalInsets.left - additionalInsets.right - directionContainerInset, height: availableSize.height - additionalInsets.top) scrollFrame = CGRect(origin: CGPoint(x: additionalInsets.left + directionContainerInset, y: additionalInsets.top), size: scrollSize) @@ -1940,7 +1950,7 @@ public final class ChatSideTopicsPanel: Component { } ))) ) - case .top: + case .top, .bottom: listItems.append(AnyComponentWithIdentity( id: ScrollId.all, component: AnyComponent(HorizontalAllItemComponent( @@ -2205,7 +2215,7 @@ public final class ChatSideTopicsPanel: Component { contextGesture: itemContextGesture ))) ) - case .top: + case .top, .bottom: listItems.append(AnyComponentWithIdentity( id: scrollId, component: AnyComponent(HorizontalItemComponent( diff --git a/submodules/TelegramUI/Components/ContextControllerImpl/Sources/ContextControllerActionsStackNode.swift b/submodules/TelegramUI/Components/ContextControllerImpl/Sources/ContextControllerActionsStackNode.swift index ddec57771f..3d9e9e9f9d 100644 --- a/submodules/TelegramUI/Components/ContextControllerImpl/Sources/ContextControllerActionsStackNode.swift +++ b/submodules/TelegramUI/Components/ContextControllerImpl/Sources/ContextControllerActionsStackNode.swift @@ -756,7 +756,7 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio private var tip: ContextController.Tip? private var tipDisposable: Disposable? - private var tipNode: InnerTextSelectionTipContainerNode? + private(set) var tipNode: InnerTextSelectionTipContainerNode? private var tipSeparatorNode: ContextControllerActionsListSeparatorItemNode? private var hapticFeedback: HapticFeedback? @@ -2378,6 +2378,9 @@ public final class ContextControllerActionsStackNodeImpl: ASDisplayNode, Context if let tipNode = itemContainer.tipNode { tipNode.animateIn() } + if let topNode = itemContainer.node as? ContextControllerActionsListStackItem.Node { + topNode.tipNode?.animateIn() + } } } diff --git a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/LegacyGlassView.swift b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/LegacyGlassView.swift index b73133d2b2..fd7a918447 100644 --- a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/LegacyGlassView.swift +++ b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/LegacyGlassView.swift @@ -179,31 +179,29 @@ final class LegacyGlassView: UIView { let displacementMagnitudeV = displacementMagnitudePoints / size.height let outerEdgeDistance = 2.0 - if let displacementMap = generateDisplacementMap(size: size, cornerRadius: cornerRadius, edgeDistance: min(12.0, cornerRadius), scale: 1.0) { - let meshTransform = generateGlassMeshFromDisplacementMap( - size: size, - cornerRadius: cornerRadius, - displacementMap: displacementMap, - displacementMagnitudeU: displacementMagnitudeU, - displacementMagnitudeV: displacementMagnitudeV, - cornerResolution: 12, - outerEdgeDistance: outerEdgeDistance, - bezier: DisplacementBezier( - x1: 0.816137566137566, - y1: 0.20502645502645533, - x2: 0.5806878306878306, - y2: 0.873015873015873 - ) - ).mesh.makeValue() - - if let meshTransform { - if !transition.animation.isImmediate, let previousTransform = backdropLayer.value(forKey: "meshTransform") as? NSObject { - backdropLayer.removeAnimation(forKey: "meshTransform") - backdropLayer.setValue(meshTransform, forKey: "meshTransform") - transition.animateMeshTransform(layer: backdropLayer, from: previousTransform, to: meshTransform) - } else { - backdropLayer.setValue(meshTransform, forKey: "meshTransform") - } + let meshTransform = generateGlassMesh( + size: size, + cornerRadius: cornerRadius, + edgeDistance: min(12.0, cornerRadius), + displacementMagnitudeU: displacementMagnitudeU, + displacementMagnitudeV: displacementMagnitudeV, + cornerResolution: 12, + outerEdgeDistance: outerEdgeDistance, + bezier: DisplacementBezier( + x1: 0.816137566137566, + y1: 0.20502645502645533, + x2: 0.5806878306878306, + y2: 0.873015873015873 + ) + ).mesh.makeValue() + + if let meshTransform { + if !transition.animation.isImmediate, let previousTransform = backdropLayer.value(forKey: "meshTransform") as? NSObject { + backdropLayer.removeAnimation(forKey: "meshTransform") + backdropLayer.setValue(meshTransform, forKey: "meshTransform") + transition.animateMeshTransform(layer: backdropLayer, from: previousTransform, to: meshTransform) + } else { + backdropLayer.setValue(meshTransform, forKey: "meshTransform") } } } diff --git a/submodules/TelegramUI/Components/HeaderPanelContainerComponent/Sources/HeaderPanelContainerComponent.swift b/submodules/TelegramUI/Components/HeaderPanelContainerComponent/Sources/HeaderPanelContainerComponent.swift index 94f7c0ca30..d4e26a4f58 100644 --- a/submodules/TelegramUI/Components/HeaderPanelContainerComponent/Sources/HeaderPanelContainerComponent.swift +++ b/submodules/TelegramUI/Components/HeaderPanelContainerComponent/Sources/HeaderPanelContainerComponent.swift @@ -258,7 +258,7 @@ public final class HeaderPanelContainerComponent: Component { } panelCount += component.panels.count var cornerRadius: CGFloat = 0.0 - if panelCount == 1 { + if panelCount == 1 && backgroundFrame.height <= 50.0 * 2.0 { cornerRadius = backgroundFrame.height * 0.5 } else { cornerRadius = 20.0 diff --git a/submodules/TelegramUI/Components/MeshTransform/Sources/GenerateMesh.swift b/submodules/TelegramUI/Components/MeshTransform/Sources/GenerateMesh.swift index e6aa570450..ba71493f6b 100644 --- a/submodules/TelegramUI/Components/MeshTransform/Sources/GenerateMesh.swift +++ b/submodules/TelegramUI/Components/MeshTransform/Sources/GenerateMesh.swift @@ -132,123 +132,39 @@ public func roundedRectGradient(x: CGFloat, y: CGFloat, width: CGFloat, height: return (nx, ny) } -/// Generates a displacement map image as a signed distance field from rounded rect edges. -/// - edgeDistance: The distance (in points) over which displacement is applied -/// - R channel: X displacement (127 = neutral, 0 = max left, 255 = max right) -/// - G channel: Y displacement (127 = neutral, 0 = max up, 255 = max down) -/// - B channel: Unused (always 0) -/// Displacement is maximum at the edge and fades linearly to zero at edgeDistance. -/// Actual displacement magnitude is applied when sampling the map. -public func generateDisplacementMap(size: CGSize, cornerRadius: CGFloat, edgeDistance: CGFloat, scale: CGFloat) -> CGImage? { - let width = Int(size.width * scale) - let height = Int(size.height * scale) - - // Clamp corner radius - let maxCornerRadius = min(size.width, size.height) / 2.0 - let clampedRadius = min(cornerRadius, maxCornerRadius) - - // Create bitmap context - var pixelData = [UInt8](repeating: 0, count: width * height * 4) - - for py in 0 ..< height { - for px in 0 ..< width { - // Convert pixel to point coordinates - let x = CGFloat(px) / scale - let y = CGFloat(py) / scale - - // Get signed distance (negative inside, positive outside) - let sdf = roundedRectSDF(x: x, y: y, width: size.width, height: size.height, cornerRadius: clampedRadius) - - // Get gradient (outward normal direction) - let (nx, ny) = roundedRectGradient(x: x, y: y, width: size.width, height: size.height, cornerRadius: clampedRadius) - - // Inward normal (content moves away from edge, toward center) - let inwardX = -nx - let inwardY = -ny - - // Distance from edge (positive inside the shape) - let distFromEdge = -sdf - - // Weight: 1 at edge, 0 at edgeDistance (linear falloff) - let weight = max(0, min(1, 1.0 - distFromEdge / edgeDistance)) - - // Displacement modulated by distance from edge - let displacementX = inwardX * weight - let displacementY = inwardY * weight - - // Encode in R/G: 127 = neutral, map -1..1 to 0..254 - let r = UInt8(max(0, min(255, Int(127 + displacementX * 127)))) - let g = UInt8(max(0, min(255, Int(127 + displacementY * 127)))) - - let idx = (py * width + px) * 4 - pixelData[idx + 0] = r // X displacement - pixelData[idx + 1] = g // Y displacement - pixelData[idx + 2] = 0 // Unused - pixelData[idx + 3] = 255 // A - } - } - - let colorSpace = CGColorSpaceCreateDeviceRGB() - guard let context = CGContext( - data: &pixelData, - width: width, - height: height, - bitsPerComponent: 8, - bytesPerRow: width * 4, - space: colorSpace, - bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue - ) else { - return nil - } - - return context.makeImage() -} - -/// Samples displacement from a displacement map with bilinear interpolation and bezier easing +/// Computes displacement at a point analytically using the rounded rect SDF. /// - Parameters: -/// - x, y: Coordinates in the displacement map's pixel space -/// - pixels: Pointer to displacement map pixel data -/// - width, height: Displacement map dimensions -/// - bytesPerRow, bytesPerPixel: Displacement map layout -/// - bezier: Bezier control points for easing curve -/// - Returns: Displacement (dx, dy) in range -1..1 with bezier easing applied -public func sampleDisplacement( +/// - x, y: Point coordinates in the shape's coordinate space +/// - width, height: Dimensions of the rounded rectangle +/// - cornerRadius: Already-clamped corner radius +/// - edgeDistance: Distance (in points) over which displacement fades from edge inward +/// - bezier: Bezier control points for easing the displacement magnitude +/// - Returns: (dx, dy) displacement in range -1..1 with bezier easing, plus the raw SDF value +public func computeDisplacement( x: CGFloat, y: CGFloat, - pixels: UnsafePointer, - width: Int, - height: Int, - bytesPerRow: Int, - bytesPerPixel: Int, + width: CGFloat, + height: CGFloat, + cornerRadius: CGFloat, + edgeDistance: CGFloat, bezier: DisplacementBezier -) -> (dx: CGFloat, dy: CGFloat) { - let clampedX = max(0, min(CGFloat(width - 1), x)) - let clampedY = max(0, min(CGFloat(height - 1), y)) +) -> (dx: CGFloat, dy: CGFloat, sdf: CGFloat) { + let sdf = roundedRectSDF(x: x, y: y, width: width, height: height, cornerRadius: cornerRadius) + let (nx, ny) = roundedRectGradient(x: x, y: y, width: width, height: height, cornerRadius: cornerRadius) - let x0 = Int(clampedX) - let y0 = Int(clampedY) - let x1 = min(x0 + 1, width - 1) - let y1 = min(y0 + 1, height - 1) + // Inward normal (content moves away from edge, toward center) + let inwardX = -nx + let inwardY = -ny - let fx = clampedX - CGFloat(x0) - let fy = clampedY - CGFloat(y0) + // Distance from edge (positive inside the shape) + let distFromEdge = -sdf - func sample(_ sx: Int, _ sy: Int) -> (r: CGFloat, g: CGFloat) { - let offset = sy * bytesPerRow + sx * bytesPerPixel - return (CGFloat(pixels[offset + 0]), CGFloat(pixels[offset + 1])) - } + // Weight: 1 at edge, 0 at edgeDistance inward (linear falloff) + let weight = max(0, min(1, 1.0 - distFromEdge / edgeDistance)) - let c00 = sample(x0, y0) - let c10 = sample(x1, y0) - let c01 = sample(x0, y1) - let c11 = sample(x1, y1) - - let r = (c00.r * (1 - fx) + c10.r * fx) * (1 - fy) + (c01.r * (1 - fx) + c11.r * fx) * fy - let g = (c00.g * (1 - fx) + c10.g * fx) * (1 - fy) + (c01.g * (1 - fx) + c11.g * fx) * fy - - // Decode: 127 = neutral, map 0..254 to -1..1 - var dx = (r - 127.0) / 127.0 - var dy = (g - 127.0) / 127.0 + // Displacement direction modulated by distance + var dx = inwardX * weight + var dy = inwardY * weight // Apply bezier easing to vector magnitude, preserving direction let mag = hypot(dx, dy) @@ -259,7 +175,7 @@ public func sampleDisplacement( dy *= scale } - return (dx, dy) + return (dx, dy, sdf) } /// Generates a glass mesh with corner-aware topology. @@ -268,10 +184,10 @@ public func sampleDisplacement( /// - 1 center patch /// Corner/edge seams share the same coordinates (but do not reuse vertices) so /// the neighbouring faces fit perfectly without T-junctions. -public func generateGlassMeshFromDisplacementMap( +public func generateGlassMesh( size: CGSize, cornerRadius: CGFloat, - displacementMap: CGImage, + edgeDistance: CGFloat, displacementMagnitudeU: CGFloat, displacementMagnitudeV: CGFloat, cornerResolution: Int, @@ -279,17 +195,6 @@ public func generateGlassMeshFromDisplacementMap( bezier: DisplacementBezier, generateWireframe: Bool = false ) -> (mesh: MeshTransform, wireframe: CGPath?) { - guard let dispDataProvider = displacementMap.dataProvider, - let dispData = dispDataProvider.data, - let dispPixels = CFDataGetBytePtr(dispData) else { - return (mesh: MeshTransform(), wireframe: nil) - } - - let dispWidth = displacementMap.width - let dispHeight = displacementMap.height - let dispBytesPerRow = displacementMap.bytesPerRow - let dispBytesPerPixel = displacementMap.bitsPerPixel / 8 - let clampedRadius = min(cornerRadius, min(size.width, size.height) / 2) let transform = MeshTransform() @@ -311,7 +216,7 @@ public func generateGlassMeshFromDisplacementMap( let usableUNorm = usableWidth / size.width let usableVNorm = usableHeight / size.height - // Helper to sample displacement and create vertex + // Helper to compute displacement analytically and create vertex func makeVertex(u: CGFloat, v: CGFloat, depth: CGFloat = 0) -> (vertex: MeshTransform.Vertex, point: CGPoint) { let mappedU = insetUOffset + u * usableUNorm let mappedV = insetVOffset + v * usableVNorm @@ -322,28 +227,27 @@ public func generateGlassMeshFromDisplacementMap( fromX = mappedU fromY = mappedV } else { - let (dispX, dispY) = sampleDisplacement( - x: mappedU * CGFloat(dispWidth - 1), - y: mappedV * CGFloat(dispHeight - 1), - pixels: dispPixels, - width: dispWidth, - height: dispHeight, - bytesPerRow: dispBytesPerRow, - bytesPerPixel: dispBytesPerPixel, + let worldX = insetPoints + u * usableWidth + let worldY = insetPoints + v * usableHeight + + let (dispX, dispY, sdf) = computeDisplacement( + x: worldX, + y: worldY, + width: size.width, + height: size.height, + cornerRadius: clampedRadius, + edgeDistance: edgeDistance, bezier: bezier ) - // Slight boost near the edge to emphasize the outer strip (rounded-corner aware) - let worldX = insetPoints + u * usableWidth - let worldY = insetPoints + v * usableHeight - let sdf = roundedRectSDF(x: worldX, y: worldY, width: size.width, height: size.height, cornerRadius: clampedRadius) - let distToEdge = max(0.0, -sdf) // distance inside the rounded rect to the edge + // Edge boost: slight displacement boost near the silhouette + let distToEdge = max(0.0, -sdf) let edgeBand = max(0.0, outerEdgeDistance) - let edgeBoostGain: CGFloat = 0.5 // up to +50% displacement at the edge, fades inside + let edgeBoostGain: CGFloat = 0.5 let edgeBoost: CGFloat if edgeBand > 0 { let t = max(0.0, min(1.0, (edgeBand - distToEdge) / edgeBand)) - let eased = t * t * (3 - 2 * t) // smoothstep + let eased = t * t * (3 - 2 * t) edgeBoost = 1.0 + eased * edgeBoostGain } else { edgeBoost = 1.0 diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SidebarBottomIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SidebarBottomIcon.imageset/Contents.json new file mode 100644 index 0000000000..a231c86ae2 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SidebarBottomIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tabsdown.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SidebarBottomIcon.imageset/tabsdown.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SidebarBottomIcon.imageset/tabsdown.pdf new file mode 100644 index 0000000000000000000000000000000000000000..359c0df43be598073b68999bb6417cf35e15e3d4 GIT binary patch literal 6924 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 diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SidebarIcon.imageset/tabsleft.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SidebarIcon.imageset/tabsleft.pdf new file mode 100644 index 0000000000000000000000000000000000000000..14472ccd1f35b64648f5c3709f1e2568677d14f6 GIT binary patch literal 6968 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 N)U^Qn_L&|w`yUyF3b_CP literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SidebarTopIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SidebarTopIcon.imageset/Contents.json new file mode 100644 index 0000000000..fa1eeb030c --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SidebarTopIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tabsup.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SidebarTopIcon.imageset/tabsup.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SidebarTopIcon.imageset/tabsup.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4504d41eab6510b10ebb536da4f114c551e55f07 GIT binary patch literal 6933 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