From bf9e4680eb466bcb7c9cb1bef567252bb1f2bb7d Mon Sep 17 00:00:00 2001
From: Runt <qingingrunt2010@qq.com>
Date: Fri, 27 Jun 2025 05:32:55 +0000
Subject: [PATCH] 1

---
 LiveProject/views/VideoRendererView.swift                                                  |   58 +----
 LiveProject/styles/TextBtnStyle.swift                                                      |    2 
 LiveProject.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved            |   15 +
 LiveProject.xcodeproj/project.pbxproj                                                      |   28 ++
 LiveProject/activity/stream/LiveActivity.swift                                             |  128 +++++++++++-
 LiveProject/enum/StreamType.swift                                                          |   23 ++
 LiveProject/tool/MetalRenderer.swift                                                       |   78 +-----
 LiveProject/data/DeviceInfo.swift                                                          |   21 ++
 LiveProject/views/FlowLayout.swift                                                         |  193 +++++++++++++++++++
 LiveProject/views/LButton.swift                                                            |    1 
 LiveProject/views/MButton.swift                                                            |    1 
 LiveProject.xcodeproj/xcuserdata/nilupeng.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist |    6 
 LiveProject/shape/IconBack.swift                                                           |   30 ++
 LiveProject/views/TitleBarView.swift                                                       |    9 
 14 files changed, 466 insertions(+), 127 deletions(-)

diff --git a/LiveProject.xcodeproj/project.pbxproj b/LiveProject.xcodeproj/project.pbxproj
index a56e128..f659319 100644
--- a/LiveProject.xcodeproj/project.pbxproj
+++ b/LiveProject.xcodeproj/project.pbxproj
@@ -6,6 +6,10 @@
 	objectVersion = 77;
 	objects = {
 
+/* Begin PBXBuildFile section */
+		A1615DB82E0D9A6E00D73CE3 /* WrappingHStack in Frameworks */ = {isa = PBXBuildFile; productRef = A1615DB72E0D9A6E00D73CE3 /* WrappingHStack */; };
+/* End PBXBuildFile section */
+
 /* Begin PBXContainerItemProxy section */
 		A1615D372E0C27F700D73CE3 /* PBXContainerItemProxy */ = {
 			isa = PBXContainerItemProxy;
@@ -42,6 +46,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				A1615DB82E0D9A6E00D73CE3 /* WrappingHStack in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -100,6 +105,7 @@
 			);
 			name = LiveProject;
 			packageProductDependencies = (
+				A1615DB72E0D9A6E00D73CE3 /* WrappingHStack */,
 			);
 			productName = LiveProject;
 			productReference = A1615D292E0C27F500D73CE3 /* LiveProject.app */;
@@ -177,6 +183,9 @@
 			);
 			mainGroup = A1615D202E0C27F500D73CE3;
 			minimizedProjectReferenceProxies = 1;
+			packageReferences = (
+				A1615DB62E0D996500D73CE3 /* XCRemoteSwiftPackageReference "WrappingHStack" */,
+			);
 			preferredProjectObjectVersion = 77;
 			productRefGroup = A1615D2A2E0C27F500D73CE3 /* Products */;
 			projectDirPath = "";
@@ -532,6 +541,25 @@
 			defaultConfigurationName = Release;
 		};
 /* End XCConfigurationList section */
+
+/* Begin XCRemoteSwiftPackageReference section */
+		A1615DB62E0D996500D73CE3 /* XCRemoteSwiftPackageReference "WrappingHStack" */ = {
+			isa = XCRemoteSwiftPackageReference;
+			repositoryURL = "https://github.com/dkk/WrappingHStack";
+			requirement = {
+				kind = upToNextMajorVersion;
+				minimumVersion = 2.2.11;
+			};
+		};
+/* End XCRemoteSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+		A1615DB72E0D9A6E00D73CE3 /* WrappingHStack */ = {
+			isa = XCSwiftPackageProductDependency;
+			package = A1615DB62E0D996500D73CE3 /* XCRemoteSwiftPackageReference "WrappingHStack" */;
+			productName = WrappingHStack;
+		};
+/* End XCSwiftPackageProductDependency section */
 	};
 	rootObject = A1615D212E0C27F500D73CE3 /* Project object */;
 }
diff --git a/LiveProject.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/LiveProject.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
new file mode 100644
index 0000000..5ac2f71
--- /dev/null
+++ b/LiveProject.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -0,0 +1,15 @@
+{
+  "originHash" : "50578bb08258a739890c5d3d0a495ba4213b27b4dbbbc844c23e93aa2d80550b",
+  "pins" : [
+    {
+      "identity" : "wrappinghstack",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/dkk/WrappingHStack",
+      "state" : {
+        "revision" : "425d9488ba55f58f0b34498c64c054c77fc2a44b",
+        "version" : "2.2.11"
+      }
+    }
+  ],
+  "version" : 3
+}
diff --git a/LiveProject.xcodeproj/xcuserdata/nilupeng.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/LiveProject.xcodeproj/xcuserdata/nilupeng.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
new file mode 100644
index 0000000..6bd2c4d
--- /dev/null
+++ b/LiveProject.xcodeproj/xcuserdata/nilupeng.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Bucket
+   uuid = "B6E33E15-95B1-4036-89E0-5C9B0FA33668"
+   type = "1"
+   version = "2.0">
+</Bucket>
diff --git a/LiveProject/activity/stream/LiveActivity.swift b/LiveProject/activity/stream/LiveActivity.swift
index d6faae2..8f16308 100644
--- a/LiveProject/activity/stream/LiveActivity.swift
+++ b/LiveProject/activity/stream/LiveActivity.swift
@@ -7,21 +7,121 @@
 
 import Foundation
 import SwiftUI
+import WrappingHStack
 
 struct LiveActivity: View {
+    @State private var pixelBuffer: CVPixelBuffer?
+    
+    
+    @State private var showDeviceDialog = false
+    
+    @State private var streamRate = Float(9/16.0);
+    @State private var mainSize: CGSize = .init(width: 100, height: 100)
+    
+    @State private var devices = [DeviceInfo(name: "相机", type: .CAMERA(), deviceId: "相机"),
+                                  DeviceInfo(name: "话筒", type: .MICROPHONE(),deviceId: "话筒"),
+                                  DeviceInfo(name: "系统", type: .SYSTEM(),deviceId : "系统")]
+    
     var body: some View {
-        NavigationView{
-            ZStack{
+        ZStack{
+            Color.clear
+                .ignoresSafeArea() // 填满全屏
+            VStack{
+                VideoRendererView(renderer: MetalRenderer()).background(Color.black).frame(width: mainSize.width,height:mainSize.height)
+                Spacer()
+            }.border(Color.blue)
+            VStack{
+                Spacer()
+                BottomBtns().frame(alignment: .bottom).border(Color.green)
+            }
+            if showDeviceDialog {
+                DialogDevices()
+            }
+        }.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
+            .background(
+                GeometryReader { geometry in
+                    Color.clear
+                        .onAppear {
+                            updateWindowSize(width: Int(geometry.size.width), height: Int(geometry.size.height))
+                        }
+                        .onChange(of: geometry.size) { newSize in
+                            updateWindowSize(width: Int(newSize.width), height: Int(newSize.height))
+                        }
+                })
+            .border(Color.red)
+            .onDisappear {
+                print("onDisappear 视图消失了!")
                 
+            }.onAppear {
+                print("onAppear 视图出现了!")
+            }
+    }
+    
+    func updateWindowSize(width:Int,height:Int){
+        var rate : Float = Float(width)/Float(height);
+        if(rate != streamRate) {
+            var mainWidth = 0;
+            var mainHeight = 0;
+            if(rate < streamRate){
+                mainWidth = width;
+                if(9.0/16 == streamRate){
+                    mainHeight = (mainWidth / 9 * 16);
+                }else{
+                    mainHeight = (mainWidth / 16 * 9);
+                }
+            }else{
+                mainHeight = height;
+                if(9.0 / 16 == streamRate){
+                    mainWidth = (mainHeight / 16 * 9);
+                }else{
+                    mainWidth = (mainHeight / 9 * 16);
+                }
+            }
+            if(mainSize.width != CGFloat(mainWidth) || mainSize.height != CGFloat(mainHeight)){
+                mainSize = .init(width: mainWidth, height: mainHeight);
+            }
+        }else{
+            if(mainSize.width != CGFloat(width) || mainSize.height != CGFloat(height)){
+                mainSize = .init(width: width, height: height);
             }
         }
+        //Log.w(TAG , "onSizeChanged: ${mainWindowSize.value}" , )
     }
-}
-
-struct Bottombtns:View {
-    var body: some View {
+    
+    func DialogDevices() -> some View{
+        ZStack{
+            Color.black.opacity(0.4)
+                .edgesIgnoringSafeArea(.all)
+                .onTapGesture {
+                    withAnimation {
+                        showDeviceDialog = false
+                    }
+                }
+            VStack {
+                Spacer()
+                VStack(spacing: 20) {
+                    Spacer().frame(height:40)
+                    FlowLayout(devices){ device in
+                        MButton(text:device.name){
+                            
+                        }
+                    }
+                    .padding()
+                    .animation(.default, value: devices)
+                }
+                .frame(maxWidth: .infinity)
+                .padding()
+                .background(Color.white)
+                .cornerRadius(20)
+                .transition(.move(edge: .bottom))
+            }
+            .zIndex(1)
+        }
+    }
+    
+    func BottomBtns() -> some View{
         VStack{
-            
+        
             HStack(){
                 MButton(icon: IconPortrait()){
                     
@@ -35,14 +135,17 @@
             }
             HStack{
                 LButton(text: "设备"){
-                    
+                    print("Click 设备 button")
+                    withAnimation{
+                        showDeviceDialog.toggle()
+                    }
                 }
                 LButton(text: "RTMP"){
                     
                 }
-                LButton(text: "文件"){
+                /*flLButton(text: "文件"){
                     
-                }
+                }*/
                 LButton(text: "文本"){
                     
                 }
@@ -56,9 +159,10 @@
     }
 }
 
-struct LiveActivity_Previews: PreviewProvider{
+
+struct LiveActivity_BottomBtns_Previews: PreviewProvider{
     static var previews: some View {
-        Bottombtns();
+        LiveActivity();
     }
     
 }
diff --git a/LiveProject/data/DeviceInfo.swift b/LiveProject/data/DeviceInfo.swift
index e69de29..e3313f4 100644
--- a/LiveProject/data/DeviceInfo.swift
+++ b/LiveProject/data/DeviceInfo.swift
@@ -0,0 +1,21 @@
+//
+//  DeviceInfo.swift
+//  LiveProject
+//
+//  Created by 倪路朋 on 6/26/25.
+//
+
+
+struct DeviceInfo : Hashable {
+    let name:String
+    let type:StreamType
+    let deviceId:String;
+    
+    func hash(into hasher: inout Hasher){
+        hasher.combine(deviceId)
+    }
+    
+    static func == (lhs: DeviceInfo, rhs: DeviceInfo) -> Bool {
+        lhs.deviceId == rhs.deviceId
+    }
+}
diff --git a/LiveProject/enum/StreamType.swift b/LiveProject/enum/StreamType.swift
index e69de29..c2c1254 100644
--- a/LiveProject/enum/StreamType.swift
+++ b/LiveProject/enum/StreamType.swift
@@ -0,0 +1,23 @@
+//
+//  StreamType.swift
+//  LiveProject
+//
+//  Created by 倪路朋 on 6/26/25.
+//
+
+
+enum StreamType{
+    case
+    //系统
+    SYSTEM(val:Int = 0),
+    //本地类型
+    FILE(val:Int = 1),
+    //图像
+    CAMERA(val:Int = 100),VIDEO(val:Int = 102),PICTURE(val:Int = 104),TEXT(val:Int = 106),
+    //音频
+    MICROPHONE(val:Int = 101),AUDIO(val:Int = 103),
+    //外置设备
+    UVC(val:Int = 301),UAC(val:Int = 302),
+    //远程
+    RTMP(val:Int = 200)
+}
diff --git a/LiveProject/shape/IconBack.swift b/LiveProject/shape/IconBack.swift
index 0856c3b..5d760f8 100644
--- a/LiveProject/shape/IconBack.swift
+++ b/LiveProject/shape/IconBack.swift
@@ -5,4 +5,32 @@
 //  Created by 倪路朋 on 6/26/25.
 //
 
-import Foundation
+import SwiftUI
+
+struct IconBack: Shape {
+    
+    func path(in rect: CGRect) -> Path {
+        var path = Path()
+        
+        let arrowHeadLength = rect.width * 0.4
+        let shaftY = rect.midY
+        let headHeight = rect.height * 0.5
+
+        // 箭头头部(三角形)
+        path.move(to: CGPoint(x: arrowHeadLength, y: rect.minY))
+        path.addLine(to: CGPoint(x: 0, y: shaftY))
+        path.addLine(to: CGPoint(x: arrowHeadLength, y: rect.maxY))
+
+        // 箭身(横线)
+        path.move(to: CGPoint(x: 0, y: shaftY))
+        path.addLine(to: CGPoint(x: rect.maxX, y: shaftY))
+        return path
+    }
+}
+
+struct IconBack_Previews : PreviewProvider{
+    static var previews: some View {
+        IconBack()
+            .stroke(Color.primary, lineWidth: 3).frame(width: 30,height: 25)
+    }
+}
diff --git a/LiveProject/styles/TextBtnStyle.swift b/LiveProject/styles/TextBtnStyle.swift
index 7c22fda..85e0369 100644
--- a/LiveProject/styles/TextBtnStyle.swift
+++ b/LiveProject/styles/TextBtnStyle.swift
@@ -11,5 +11,7 @@
 struct TextBtnStyle: ButtonStyle {
     func makeBody(configuration: Configuration) -> some View {
         configuration.label
+            .scaleEffect(configuration.isPressed ? 0.95 : 1.0)
+            .opacity(configuration.isPressed ? 0.8 : 1.0)
     }
 }
diff --git a/LiveProject/tool/MetalRenderer.swift b/LiveProject/tool/MetalRenderer.swift
index 3e88871..791dfc8 100644
--- a/LiveProject/tool/MetalRenderer.swift
+++ b/LiveProject/tool/MetalRenderer.swift
@@ -4,75 +4,35 @@
 //  渲染工具
 //  Created by 倪路朋 on 6/26/25.
 //
+import CoreVideo
 import Metal
 import MetalKit
 
-class Renderer: NSObject, MTKViewDelegate {
-    static var shared: Renderer?
+class MetalRenderer: NSObject, MTKViewDelegate {
+    var device: MTLDevice!
+    var commandQueue: MTLCommandQueue!
 
-    private var device: MTLDevice!
-    private var commandQueue: MTLCommandQueue!
-    private var pipelineState: MTLRenderPipelineState!
-    private var texture: MTLTexture?
-    private var textureDescriptor: MTLTextureDescriptor!
-    private var currentData: [UInt8]?
-    private var textureWidth = 0
-    private var textureHeight = 0
-
-    weak var mtkView: MTKView? {
-        didSet {
-            guard let view = mtkView else { return }
-            device = view.device
-            commandQueue = device.makeCommandQueue()
-
-            let library = device.makeDefaultLibrary()
-            let pipelineDesc = MTLRenderPipelineDescriptor()
-            pipelineDesc.vertexFunction = library?.makeFunction(name: "vertexShader")
-            pipelineDesc.fragmentFunction = library?.makeFunction(name: "fragmentShader")
-            pipelineDesc.colorAttachments[0].pixelFormat = view.colorPixelFormat
-
-            pipelineState = try? device.makeRenderPipelineState(descriptor: pipelineDesc)
-
-            Renderer.shared = self
-        }
+    func setup(view: MTKView) {
+        self.device = view.device
+        self.commandQueue = device.makeCommandQueue()
+        // 初始化 texture、pipeline 等
     }
 
-    func updateFrame(data: [UInt8], width: Int, height: Int) {
-        currentData = data
-        textureWidth = width
-        textureHeight = height
+    // ✅ 必须实现的方法 1:窗口大小改变时调用
+    func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
+        // 可以留空或更新视图缩放、渲染区域等
     }
 
+    // ✅ 必须实现的方法 2:每一帧绘制时调用
     func draw(in view: MTKView) {
         guard let drawable = view.currentDrawable,
-              let descriptor = view.currentRenderPassDescriptor,
-              let data = currentData else { return }
+              let descriptor = view.currentRenderPassDescriptor else { return }
 
-        if texture == nil || texture?.width != textureWidth || texture?.height != textureHeight {
-            textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(
-                pixelFormat: .rgba8Unorm,
-                width: textureWidth,
-                height: textureHeight,
-                mipmapped: false
-            )
-            textureDescriptor.usage = [.shaderRead, .shaderWrite]
-            texture = device.makeTexture(descriptor: textureDescriptor)
-        }
-
-        let region = MTLRegionMake2D(0, 0, textureWidth, textureHeight)
-        data.withUnsafeBytes { ptr in
-            texture?.replace(region: region, mipmapLevel: 0, withBytes: ptr.baseAddress!, bytesPerRow: textureWidth * 4)
-        }
-
-        let commandBuffer = commandQueue.makeCommandBuffer()
-        let encoder = commandBuffer?.makeRenderCommandEncoder(descriptor: descriptor)
-        encoder?.setRenderPipelineState(pipelineState)
-        encoder?.setFragmentTexture(texture, index: 0)
-        encoder?.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
-        encoder?.endEncoding()
-        commandBuffer?.present(drawable)
-        commandBuffer?.commit()
+        let commandBuffer = commandQueue.makeCommandBuffer()!
+        let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor)!
+        // 渲染逻辑,如绘制纹理
+        encoder.endEncoding()
+        commandBuffer.present(drawable)
+        commandBuffer.commit()
     }
-
-    func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {}
 }
diff --git a/LiveProject/views/FlowLayout.swift b/LiveProject/views/FlowLayout.swift
index f3ee86a..151cd68 100644
--- a/LiveProject/views/FlowLayout.swift
+++ b/LiveProject/views/FlowLayout.swift
@@ -4,4 +4,197 @@
 //
 //  Created by 倪路朋 on 6/26/25.
 //
+import SwiftUI
+import SwiftUI
 
+/// 完全自定义的自动换行布局容器
+struct FlowLayout<Data: RandomAccessCollection, Content: View>: View where Data.Element: Hashable {
+    // MARK: - 属性
+    
+    /// 要显示的数据集合
+    let data: Data
+    /// 水平间距
+    let horizontalSpacing: CGFloat
+    /// 垂直间距
+    let verticalSpacing: CGFloat
+    /// 对齐方式
+    let alignment: HorizontalAlignment
+    /// 内容构建闭包
+    let content: (Data.Element) -> Content
+    /// 总高度状态
+    @State private var totalHeight: CGFloat = 0
+    
+    // MARK: - 初始化
+    
+    /// 初始化FlowLayout
+    /// - Parameters:
+    ///   - data: 要显示的数据集合
+    ///   - horizontalSpacing: 水平间距,默认为8
+    ///   - verticalSpacing: 垂直间距,默认为8
+    ///   - alignment: 对齐方式,默认为.leading
+    ///   - content: 内容构建闭包
+    init(
+        _ data: Data,
+        horizontalSpacing: CGFloat = 8,
+        verticalSpacing: CGFloat = 8,
+        alignment: HorizontalAlignment = .leading,
+        @ViewBuilder content: @escaping (Data.Element) -> Content
+    ) {
+        self.data = data
+        self.horizontalSpacing = horizontalSpacing
+        self.verticalSpacing = verticalSpacing
+        self.alignment = alignment
+        self.content = content
+    }
+    
+    // MARK: - 主体视图
+    
+    var body: some View {
+        GeometryReader { geometry in
+            self.contentView(in: geometry)
+                .background(
+                    HeightReader(height: $totalHeight)
+                )
+        }
+        .frame(height: totalHeight)
+    }
+    
+    // MARK: - 私有方法
+    
+    /// 构建内容视图
+    private func contentView(in geometry: GeometryProxy) -> some View {
+        var width: CGFloat = 0
+        var height: CGFloat = 0
+        var lastHeight: CGFloat = 0
+        
+        return ZStack(alignment: Alignment(horizontal: alignment, vertical: .top)) {
+            ForEach(data.map { $0 }, id: \.self) { item in
+                content(item)
+                    .padding(.trailing, horizontalSpacing)
+                    .padding(.bottom, verticalSpacing)
+                    .alignmentGuide(.leading) { dimensions in
+                        // 检查是否需要换行
+                        if abs(width - dimensions.width) > geometry.size.width {
+                            width = 0
+                            height += lastHeight + verticalSpacing
+                        }
+                        
+                        let result = width
+                        
+                        // 更新宽度计算
+                        if item == data.last {
+                            width = 0 // 重置为0,最后一项
+                        } else {
+                            width -= dimensions.width + horizontalSpacing
+                        }
+                        
+                        // 记录当前行高度
+                        lastHeight = dimensions.height
+                        return result
+                    }
+                    .alignmentGuide(.top) { dimensions in
+                        let result = height
+                        
+                        // 如果是最后一项,更新总高度
+                        if item == data.last {
+                            height += lastHeight + verticalSpacing
+                        }
+                        
+                        return result
+                    }
+            }
+        }
+    }
+}
+
+// MARK: - 高度读取器
+
+/// 用于读取视图高度的辅助视图
+private struct HeightReader: View {
+    @Binding var height: CGFloat
+    
+    var body: some View {
+        GeometryReader { geometry in
+            Color.clear
+                .preference(
+                    key: HeightPreferenceKey.self,
+                    value: geometry.size.height
+                )
+        }
+        .onPreferenceChange(HeightPreferenceKey.self) { newHeight in
+            DispatchQueue.main.async {
+                self.height = newHeight
+            }
+        }
+    }
+}
+
+// MARK: - 高度偏好键
+
+/// 用于传递高度值的PreferenceKey
+private struct HeightPreferenceKey: PreferenceKey {
+    static var defaultValue: CGFloat = 0
+    
+    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
+        value = nextValue()
+    }
+}
+
+// MARK: - 使用示例
+
+struct FlowLayoutExample: View {
+    let tags = [
+        "Swift", "SwiftUI", "UIKit", "Combine", "Core Data",
+        "Xcode", "Interface Builder", "Core Animation", "ARKit",
+        "Metal", "Core ML", "Vision", "MapKit", "CloudKit"
+    ]
+    
+    @State private var newTag = ""
+    @State private var customTags = ["自定义标签1", "自定义标签2"]
+    
+    var body: some View {
+        VStack {
+            FlowLayout(customTags + tags, horizontalSpacing: 10, verticalSpacing: 10) { tag in
+                MButton(text:tag){
+                    
+                }
+            }
+            .padding()
+            .animation(.default, value: customTags)
+        }
+    }
+    
+    private func addTag() {
+        withAnimation {
+            customTags.append(newTag)
+            newTag = ""
+        }
+    }
+}
+
+/// 标签视图
+struct TagView: View {
+    let text: String
+    
+    var body: some View {
+        Text(text)
+            .padding(.horizontal, 12)
+            .padding(.vertical, 8)
+            .background(Color.blue.opacity(0.2))
+            .foregroundColor(.blue)
+            .cornerRadius(10)
+            .overlay(
+                RoundedRectangle(cornerRadius: 10)
+                    .stroke(Color.blue, lineWidth: 1)
+            )
+            .fixedSize(horizontal: true, vertical: false) // 确保文本不被截断
+    }
+}
+
+// MARK: - 预览
+
+struct FlowLayout_Previews: PreviewProvider {
+    static var previews: some View {
+        FlowLayoutExample()
+    }
+}
diff --git a/LiveProject/views/LButton.swift b/LiveProject/views/LButton.swift
index 932ff21..6f430e2 100644
--- a/LiveProject/views/LButton.swift
+++ b/LiveProject/views/LButton.swift
@@ -21,7 +21,6 @@
     }
     
     var body: some View {
-        //登录按钮
         Button(action: {
             if(valid == .INVALID){
                 return
diff --git a/LiveProject/views/MButton.swift b/LiveProject/views/MButton.swift
index 06da285..ce8b915 100644
--- a/LiveProject/views/MButton.swift
+++ b/LiveProject/views/MButton.swift
@@ -21,7 +21,6 @@
     }
     
     var body: some View {
-        //登录按钮
         Button(action: {
             if(valid == .INVALID){
                 return
diff --git a/LiveProject/views/TitleBarView.swift b/LiveProject/views/TitleBarView.swift
index a58fd40..7cfce53 100644
--- a/LiveProject/views/TitleBarView.swift
+++ b/LiveProject/views/TitleBarView.swift
@@ -12,9 +12,9 @@
     @Environment(\.presentationMode) var presentationMode
     
     var title = ""
-    var imgLeft = "IconBack";
+    var iconBack  = IconBack();
     var imgRight = "";
-    var titleColor = "ColorText"
+    var titleColor = Color.colorText
     
     var body: some View {
         
@@ -24,11 +24,10 @@
                     print("Click back button")
                     self.presentationMode.wrappedValue.dismiss()
                 }) {
-                    Image(imgLeft)
-                        .frame(width: 28,height: 28)
+                    iconBack.stroke(Color.primary, lineWidth: 2.5).frame(width: 18,height: 14)
                 }
                 Spacer()
-                Text(title).foregroundColor(Color.init(titleColor))
+                Text(title).foregroundColor(titleColor)
                     .bold().font(Font.system(size: 16))
                 Spacer()
                 Image(imgRight).frame(width: 28,height: 28)
diff --git a/LiveProject/views/VideoRendererView.swift b/LiveProject/views/VideoRendererView.swift
index 960aa78..f3b0341 100644
--- a/LiveProject/views/VideoRendererView.swift
+++ b/LiveProject/views/VideoRendererView.swift
@@ -7,57 +7,19 @@
 import SwiftUI
 import MetalKit
 
-struct MetalVideoRenderView: UIViewRepresentable {
-    // 统一接口,接收各种视频源的帧数据
-    var frameData: VideoFrameData?
-    
-    func makeCoordinator() -> Coordinator {
-        Coordinator(self)
-    }
-    
+struct VideoRendererView: UIViewRepresentable {
+    let renderer: MetalRenderer  // 自定义 Metal 渲染器,支持传入 RGBA/YUV 数据帧
+
     func makeUIView(context: Context) -> MTKView {
         let mtkView = MTKView()
-        mtkView.device = context.coordinator.device
-        mtkView.delegate = context.coordinator
+        mtkView.device = MTLCreateSystemDefaultDevice()
         mtkView.framebufferOnly = false
-        mtkView.colorPixelFormat = .bgra8Unorm
-        mtkView.autoResizeDrawable = true
+        mtkView.enableSetNeedsDisplay = false
+        mtkView.isPaused = true
+        mtkView.delegate = renderer
+        renderer.setup(view: mtkView)
         return mtkView
     }
-    
-    func updateUIView(_ uiView: MTKView, context: Context) {
-        // 更新帧数据
-        context.coordinator.updateFrame(frameData)
-    }
-    
-    class Coordinator: NSObject, MTKViewDelegate {
-        var parent: MetalVideoRenderView
-        var device: MTLDevice
-        var commandQueue: MTLCommandQueue
-        var pipelineState: MTLRenderPipelineState
-        var textureCache: CVMetalTextureCache?
-        
-        // YUV转RGB的着色器
-        var yuvConversionPipeline: MTLComputePipelineState?
-        
-        init(_ parent: MetalVideoRenderView) {
-            self.parent = parent
-            self.device = MTLCreateSystemDefaultDevice()!
-            self.commandQueue = device.makeCommandQueue()!
-            
-            // 初始化Metal资源
-            // (这里需要设置渲染管线和纹理缓存)
-        }
-        
-        func updateFrame(_ frameData: VideoFrameData?) {
-            // 根据不同的帧数据格式(YUV/RGBA)进行处理
-            // 将数据转换为Metal纹理
-        }
-        
-        func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {}
-        
-        func draw(in view: MTKView) {
-            // 执行Metal绘制命令
-        }
-    }
+
+    func updateUIView(_ uiView: MTKView, context: Context) {}
 }

--
Gitblit v1.9.1