Blender4iOS/Sources/Viewport/ViewportView.swift

115 lines
3.4 KiB
Swift
Raw Normal View History

2026-04-10 21:34:23 -07:00
// ViewportView.swift
// A single viewport pane MTKView wrapper with gesture handling.
import SwiftUI
import MetalKit
// MARK: - Single Viewport Pane
struct ViewportPane: View {
let renderer: ViewportRenderer
let label: String
var body: some View {
GeometryReader { geo in
ZStack(alignment: .topLeading) {
MetalViewRepresentable(
renderer: renderer,
size: geo.size
)
.ignoresSafeArea()
.gesture(orbitGesture)
.gesture(zoomGesture)
.onTapGesture(count: 2) {
renderer.camera.reset()
}
// Viewport label
Text(label)
.font(.caption2.weight(.medium))
.foregroundStyle(.white.opacity(0.5))
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(.black.opacity(0.3), in: RoundedRectangle(cornerRadius: 4))
.padding(8)
}
}
}
private var orbitGesture: some Gesture {
DragGesture(minimumDistance: 1)
.onChanged { value in
renderer.camera.orbit(
deltaX: Float(value.translation.width) * 0.15,
deltaY: Float(value.translation.height) * 0.15
)
}
}
private var zoomGesture: some Gesture {
MagnifyGesture()
.onChanged { value in
renderer.camera.zoom(scale: Float(value.magnification))
}
}
}
// MARK: - UIViewRepresentable
struct MetalViewRepresentable: UIViewRepresentable {
let renderer: ViewportRenderer
let size: CGSize
func makeCoordinator() -> ViewportCoordinator {
ViewportCoordinator(renderer: renderer)
}
func makeUIView(context: Context) -> MTKView {
guard let device = renderer.device ?? MTLCreateSystemDefaultDevice() else {
fatalError("Metal is not supported on this device.")
}
let mtkView = MTKView(frame: .zero, device: device)
mtkView.delegate = context.coordinator
mtkView.colorPixelFormat = .bgra8Unorm_srgb
mtkView.depthStencilPixelFormat = .depth32Float
mtkView.clearColor = MTLClearColor(red: 0.224, green: 0.224, blue: 0.224, alpha: 1.0)
mtkView.preferredFramesPerSecond = 120
mtkView.enableSetNeedsDisplay = false
mtkView.isPaused = false
mtkView.framebufferOnly = false
mtkView.isMultipleTouchEnabled = true
return mtkView
}
func updateUIView(_ mtkView: MTKView, context: Context) {
let scale = mtkView.contentScaleFactor
mtkView.drawableSize = CGSize(
width: size.width * scale,
height: size.height * scale
)
}
}
// MARK: - Coordinator
final class ViewportCoordinator: NSObject, MTKViewDelegate {
let renderer: ViewportRenderer
init(renderer: ViewportRenderer) {
self.renderer = renderer
}
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
renderer.viewportSize = SIMD2<Float>(Float(size.width), Float(size.height))
}
func draw(in view: MTKView) {
guard let drawable = view.currentDrawable,
let descriptor = view.currentRenderPassDescriptor
else { return }
renderer.draw(drawable: drawable, passDescriptor: descriptor)
}
}