Skip to content

Commit

Permalink
Open source
Browse files Browse the repository at this point in the history
  • Loading branch information
tattn committed Oct 21, 2023
1 parent a779f32 commit e76cc6e
Show file tree
Hide file tree
Showing 15 changed files with 753 additions and 15 deletions.
4 changes: 3 additions & 1 deletion app/xcode/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ let package = Package(
"VCamUIFoundation", "VCamTracking", "VCamCamera", "VCamData", "VCamLocalization", "VCamBridge",
], resources: [
.process("Resources"),
], swiftSettings: [
.define("ENABLE_MOCOPI"),
]),
.target(name: "VCamUIFoundation"),
.target(name: "VCamData", dependencies: ["VCamBridge", "VCamEntity"]),
.target(name: "VCamEntity", dependencies: ["VCamDefaults", "VCamLocalization"], swiftSettings: [
.define("ENABLE_MOCOPI")
.define("ENABLE_MOCOPI"),
]),
.target(name: "VCamLocalization", resources: [.process("VCamResources")]),
.target(name: "VCamMedia", dependencies: ["VCamEntity", "VCamAppExtension", "VCamLogger"]),
Expand Down
8 changes: 4 additions & 4 deletions app/xcode/Sources/VCamTracking/Tracking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ public func uniSupportsPerfectSync() -> Bool {
Tracking.shared.faceTrackingMethod.supportsPerfectSync
}

public final class Tracking {
public final class Tracking: ObservableObject {
public static let shared = Tracking()

public private(set) var faceTrackingMethod = TrackingMethod.Face.default
public private(set) var handTrackingMethod = TrackingMethod.Hand.default
public private(set) var fingerTrackingMethod = TrackingMethod.Finger.default
@Published public private(set) var faceTrackingMethod = TrackingMethod.Face.default
@Published public private(set) var handTrackingMethod = TrackingMethod.Hand.default
@Published public private(set) var fingerTrackingMethod = TrackingMethod.Finger.default

public private(set) var useEyeTracking = false
public private(set) var useVowelEstimation = false
Expand Down
4 changes: 3 additions & 1 deletion app/xcode/Sources/VCamUI/DeviceAuthorization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ public enum DeviceAuthorization {

case .notDetermined:
AVCaptureDevice.requestAccess(for: type.mediaType) { _ in
requestAuthorization(type: type, completion: completion)
Task { @MainActor in
requestAuthorization(type: type, completion: completion)
}
}

case .denied, .restricted:
Expand Down
3 changes: 0 additions & 3 deletions app/xcode/Sources/VCamUI/MacWindowManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,6 @@ public final class MacWindowManager {

private var openWindows: [String: NSWindow] = [:]

public var openSettings = {}
public var openSettingsVirtualCamera = {}

public func open<T: MacWindow>(_ windowView: T) {
let id = self.id(T.self)
if let window = openWindows[id] {
Expand Down
4 changes: 1 addition & 3 deletions app/xcode/Sources/VCamUI/Migration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import VCamCamera
import AppKit

public struct Migration {
public static var openVirtualCameraPreference: () -> Void = {}

public static func migrate() async {
let previousVersion = UserDefaults.standard.value(for: .previousVersion)

Expand Down Expand Up @@ -99,7 +97,7 @@ extension Migration {
}

if !isNewCameraInstalled {
openVirtualCameraPreference()
MacWindowManager.shared.open(VCamSettingView(tab: .virtualCamera))
}
}

Expand Down
12 changes: 12 additions & 0 deletions app/xcode/Sources/VCamUI/Settings/VCamSettingExperimentView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// VCamSettingExperimentView.swift
//
//
// Created by Tatsuya Tanaka on 2023/10/21.
//

import SwiftUI

public enum VCamSettingExperimentView {
public static var make: () -> AnyView = { fatalError() }
}
105 changes: 105 additions & 0 deletions app/xcode/Sources/VCamUI/Settings/VCamSettingTrackingView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//
// VCamSettingTrackingView.swift
//
//
// Created by Tatsuya Tanaka on 2023/02/14.
//

import SwiftUI
import AVFoundation
import VCamEntity
import VCamCamera
import VCamTracking
import VCamBridge

public struct VCamSettingTrackingView: View {
public init() {}

@AppStorage(key: .cameraFps) private var cameraFps
@AppStorage(key: .moveXIntensity) private var moveXIntensity
@AppStorage(key: .eyeTrackingIntensityX) private var eyeTrackingIntensityX
@AppStorage(key: .eyeTrackingIntensityY) private var eyeTrackingIntensityY
@AppStorage(key: .useVowelEstimation) private var useVowelEstimation
@AppStorage(key: .useEyeTracking) private var useEyeTracking
@AppStorage(key: .fingerTrackingOpenIntensity) private var fingerTrackingOpenIntensity
@AppStorage(key: .fingerTrackingCloseIntensity) private var fingerTrackingCloseIntensity
#if ENABLE_MOCOPI
@AppStorage(key: .integrationMocopi) private var integrationMocopi
#endif

@ExternalStateBinding(.lipSyncMicIntensity) private var lipSyncMicIntensity
@ExternalStateBinding(.shoulderRotationWeight) private var shoulderRotationWeight
@ExternalStateBinding(.swivelOffset) private var swivelOffset

@StateObject private var toggleWorkaround = ToggleWorkaround()

public var body: some View {
VStack {
VCamTrackingView()
TrackingDeviceView()

GroupBox {
VStack(alignment: .leading) {
Toggle(isOn: $useVowelEstimation) {
Text(L10n.useVowelEstimation.key, bundle: .localize)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
}

GroupBox {
Form {
ValueEditField(L10n.lipSyncSensitivity.key, value: $lipSyncMicIntensity, type: .slider(0.1...3))
ValueEditField(L10n.fpsCamera.key, value: $cameraFps.map(), type: .slider(1...60) { isEditing in
guard !isEditing else { return }
Tracking.shared.avatarCameraManager.setFPS(cameraFps)
})
}
}

GroupBox {
Form {
ValueEditField(L10n.easeOfBodyMovement.key, value: $moveXIntensity.map(), type: .slider(0.1...3))
ValueEditField(L10n.shoulderRotationWeight.key, value: $shoulderRotationWeight, type: .slider(0.0...1))
ValueEditField(L10n.swivelOffset.key, value: $swivelOffset, type: .slider(0.0...30))
}
}
#if ENABLE_MOCOPI
.disabled(integrationMocopi)
#endif
GroupBox {
VStack(alignment: .leading) {
Toggle(isOn: $useEyeTracking) {
Text(L10n.trackEyes.key, bundle: .localize)
}
Group {
ValueEditField(L10n.eyesHorizontalSensitivity.key, value: $eyeTrackingIntensityX.map(), type: .slider(0...10))
ValueEditField(L10n.eyesVerticalSensitivity.key, value: $eyeTrackingIntensityY.map(), type: .slider(0...10))
}
.disabled(!useEyeTracking)
}
}
GroupBox {
VStack {
ValueEditField(L10n.easeOfOpeningFingers.key, value: $fingerTrackingOpenIntensity.map(), type: .slider(0.1...3))
ValueEditField(L10n.easeOfCloseFingers.key, value: $fingerTrackingCloseIntensity.map(), type: .slider(0.1...3))
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}
.onAppear {
toggleWorkaround.run()
}
}
}

private final class ToggleWorkaround: ObservableObject {
// Workaround: The UI fails to refresh when using checkbox-style toggle, requiring a one-time rebuild of the UI for updates to take effect.
func run() {
objectWillChange.send()
}
}

#Preview {
VCamSettingTrackingView()
}
132 changes: 132 additions & 0 deletions app/xcode/Sources/VCamUI/Settings/VCamSettingView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
//
// VCamSettingView.swift
//
//
// Created by Tatsuya Tanaka on 2023/02/12.
//

import SwiftUI
import VCamEntity
import VCamCamera
import VCamLocalization
import AVFoundation

public struct VCamSettingView: View {
public enum Tab: Hashable, Identifiable, CaseIterable {
case general
case rendering
case tracking
case virtualCamera
case integration
case experiment

public var id: Self { self }

var title: LocalizedStringKey {
switch self {
case .general:
L10n.general.key
case .rendering:
L10n.rendering.key
case .tracking:
L10n.tracking.key
case .virtualCamera:
L10n.virtualCamera.key
case .integration:
L10n.integration.key
case .experiment:
L10n.experiment.key
}
}

var icon: Image {
switch self {
case .general:
Image(systemName: "hammer.fill")
case .rendering:
Image(systemName: "display")
case .tracking:
if #available(macOS 14, *) {
Image(systemName: "figure")
} else {
Image(systemName: "figure.arms.open")
}
case .virtualCamera:
Image(systemName: "camera.fill")
case .integration:
Image(systemName: "app.connected.to.app.below.fill")
case .experiment:
Image(systemName: "exclamationmark.triangle.fill")
}
}
}

@State var tab: Tab?

@ObservedObject private var recorder = VideoRecorder.shared

@AppStorage(key: .locale) var locale

public init(tab: Tab = .general) {
self._tab = State(initialValue: tab)
}

public var body: some View {
HStack {
List(selection: $tab) {
ForEach(Tab.allCases) { tab in
Label {
Text(tab.title, bundle: .localize)
.font(.callout)
} icon: {
tab.icon
}
}
}
.listStyle(.sidebar)
.frame(width: 140)

VStack {
switch tab {
case .general:
VCamSettingGeneralView()
case .rendering:
VCamSettingRenderingView()
case .tracking:
VCamSettingTrackingView()
case .virtualCamera:
VCamSettingVirtualCameraView()
case .integration:
VCamSettingsIntegrationView()
case .experiment:
VCamSettingExperimentView.make()
case nil:
Text("🥹")
}
Spacer()
}
.frame(minWidth: 440)
}
.padding([.top, .trailing], 8)
.onChange(of: tab) { newValue in
if newValue == nil {
tab = tab
}
}
.environment(\.locale, locale.isEmpty ? .current : Locale(identifier: locale))
.disabled(recorder.isRecording)
}
}

extension VCamSettingView: MacWindow {
public var windowTitle: String { L10n.settings.text }

public func configureWindow(_ window: NSWindow) -> NSWindow {
window.level = .floating
return window
}
}

#Preview {
VCamSettingView(tab: .general)
}
Loading

0 comments on commit e76cc6e

Please sign in to comment.