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 ae53580 commit 30b641d
Show file tree
Hide file tree
Showing 14 changed files with 306 additions and 45 deletions.
12 changes: 2 additions & 10 deletions app/xcode/App/VCamUIPreview/VCamUIPreviewApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
@MainActor
private func configureApp() async {
VCamUIPreviewStub.stub()
VCamSystem.shared.configure()

// TODO: Refactor

await Migration.migrate()
WindowManager.shared.setUpView()

Camera.configure()
AudioDevice.configure()
try? SceneManager.shared.loadCurrentScene()

Tracking.shared.configure()

WindowManager.shared.system.isUniVCamSystemEnabled = true

VirtualCameraManager.shared.startCameraExtension()
VCamSystem.shared.isUniVCamSystemEnabled = true
}
}
2 changes: 1 addition & 1 deletion app/xcode/Sources/VCamCamera/CoreMediaSinkStream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public final class CoreMediaSinkStream: NSObject {
size: .init(width: 1280, height: 720)
).ciImage!

VirtualCameraManager.shared.sendImageToVirtualCamera(with: debugImage, useHMirror: false)
VirtualCameraManager.shared.sendImageToVirtualCamera(with: debugImage)
}
#endif

Expand Down
19 changes: 16 additions & 3 deletions app/xcode/Sources/VCamCamera/VirtualCameraManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,28 @@
import Foundation
import CoreImage
import VCamDefaults
import Combine

public final class VirtualCameraManager {
public static let shared = VirtualCameraManager()

public let sinkStream = CoreMediaSinkStream()
private var useHMirror = false
private var cancellables: Set<AnyCancellable> = []

public func sendImageToVirtualCamera(with image: CIImage, useHMirror: Bool) {
private init() {
UserDefaults.standard.publisher(for: \.vc_use_hmirror, options: [.initial, .new])
.sink { [unowned self] in useHMirror = $0 }
.store(in: &cancellables)
}

public func sendImageToVirtualCamera(with image: CIImage) {
guard sinkStream.isStarting else { return }
let result = processImage(image, useHMirror: useHMirror)
let result = processImage(image)
sinkStream.render(result)
}

private func processImage(_ image: CIImage, useHMirror: Bool) -> CIImage {
private func processImage(_ image: CIImage) -> CIImage {
var processedImage = image

if useHMirror {
Expand All @@ -44,3 +53,7 @@ public final class VirtualCameraManager {
sinkStream.start()
}
}

private extension UserDefaults {
@objc dynamic var vc_use_hmirror: Bool { value(for: .useHMirror) }
}
1 change: 0 additions & 1 deletion app/xcode/Sources/VCamData/UserDefaultsKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ public extension UserDefaults.Key {
static var trackingMethodFace: Key<TrackingMethod.Face> { .init("vc_tracking_method_face", default: .default) }
static var trackingMethodHand: Key<TrackingMethod.Hand> { .init("vc_tracking_method_hand", default: .default) }
static var trackingMethodFinger: Key<TrackingMethod.Finger> { .init("vc_tracking_method_finger", default: .default) }
static var moveXIntensity: Key<Double> { .init("vc_move_x_intensity", default: 1) }
static var eyeTrackingOffsetY: Key<Double> { .init("vc_eye_tracking_offset_y", default: -0.2) }
static var eyeTrackingIntensityX: Key<Double> { .init("vc_eye_tracking_intensity_x", default: 6) }
static var eyeTrackingIntensityY: Key<Double> { .init("vc_eye_tracking_intensity_y", default: 3) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,6 @@
"eyesVerticalSensitivity" = "Vertical sensitivity";
"easeOfOpeningFingers" = "Finger opening sensitivity";
"easeOfCloseFingers" = "Finger closing sensitivity";
"easeOfBodyMovement" = "Horizontal sensitivity of body";
"shoulderRotationWeight" = "Weight of shoulder rotation";
"swivelOffset" = "Elbow offset";
"faceEyeMouth" = "Face / Eye / Mouth";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,6 @@
"eyesVerticalSensitivity" = "目の上下の動きの感度";
"easeOfOpeningFingers" = "指の開きやすさ";
"easeOfCloseFingers" = "指の閉じやすさ";
"easeOfBodyMovement" = "体の左右の動きの大きさ";
"shoulderRotationWeight" = "肩の回転の大きさ";
"swivelOffset" = "肘の調整";
"faceEyeMouth" = "顔・目・口";
Expand Down
228 changes: 228 additions & 0 deletions app/xcode/Sources/VCamUI/AppMenu.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
//
// AppMenu.swift
//
//
// Created by Tatsuya Tanaka on 2022/02/07.
//

import Foundation
import AppKit
import VCamUIFoundation
import VCamBridge
import VCamData
import VCamCamera
import VCamTracking
import VCamLogger
import SystemExtensions

public final class AppMenu: NSObject {
public static let shared = AppMenu()

public let menu: NSMenu

private override init() {
let mainMenu: NSMenu
if VCamSystem.shared.windowManager.isUnity {
menu = NSApp.mainMenu!
mainMenu = menu.items[0].submenu!
} else {
menu = NSMenu()
mainMenu = Self.makeSubMenu(menu: menu, title: "VCam", items: [])
NSApp.mainMenu = menu
}

super.init()

setupMainMenu(subMenu: mainMenu)
setupFileMenu(subMenu: menu)
setupEditMenu(subMenu: menu)
setupObjectMenu(subMenu: menu)
setupAvatarMenu(subMenu: menu)
setupWindowMenu(subMenu: menu)
setupHelpMenu(subMenu: menu)
}

public func configure() {}

private func makeMenuItem(title: String, action: Selector, keyEquivalent: String = "") -> NSMenuItem {
let item = NSMenuItem(title: title, action: action, keyEquivalent: keyEquivalent)
item.target = self
return item
}

@discardableResult
public static func makeSubMenu(menu: NSMenu, title: String, items: [NSMenuItem]) -> NSMenu {
let rootItem = NSMenuItem()
menu.addItem(rootItem)
let subMenu = NSMenu(title: title)
rootItem.submenu = subMenu
subMenu.items = items
return subMenu
}

private func setupEditMenu(subMenu: NSMenu) {
Self.makeSubMenu(menu: subMenu, title: L10n.edit.text, items: [
NSMenuItem(title: L10n.cut.text, action: #selector(NSText.copy(_:)), keyEquivalent: "x"),
NSMenuItem(title: L10n.copy.text, action: #selector(NSText.copy(_:)), keyEquivalent: "c"),
NSMenuItem(title: L10n.paste.text, action: #selector(NSText.paste(_:)), keyEquivalent: "v"),
NSMenuItem(title: L10n.selectAll.text, action: #selector(NSText.selectAll(_:)), keyEquivalent: "a"),
])
}
}

// MARK: - Main
private extension AppMenu {
private func setupMainMenu(subMenu: NSMenu) {
let quitItem = makeMenuItem(title: L10n.quitVCam.text, action: #selector(quit), keyEquivalent: "q")
subMenu.items.insert(quitItem, at: 0)
subMenu.items.insert(.separator(), at: 0)
let updateItem = makeMenuItem(title: L10n.checkForUpdates.text, action: #selector(checkUpdates))
subMenu.items.insert(updateItem, at: 0)
subMenu.items.insert(.separator(), at: 0)
let preferenceItem = makeMenuItem(title: L10n.settings.text, action: #selector(openPreferences), keyEquivalent: ",")
subMenu.items.insert(preferenceItem, at: 0)
let aboutItem = makeMenuItem(title: L10n.aboutApp.text, action: #selector(about))
subMenu.items.insert(aboutItem, at: 0)
}

@objc private func quit() {
Logger.log("")
UniBridge.shared.quitApp()
}

@objc private func about() {
MacWindowManager.shared.openCredits()
}

@objc private func openPreferences() {
MacWindowManager.shared.open(VCamSettingView())
}

@objc private func checkUpdates() {
Task {
await AppUpdater.vcam.presentUpdateAlert()
}
}
}

// MARK: - File
private extension AppMenu {
private func setupFileMenu(subMenu: NSMenu) {
Self.makeSubMenu(menu: subMenu, title: L10n.file.text, items: [
makeMenuItem(title: L10n.loadVRMFile.text, action: #selector(loadVRM)),
makeMenuItem(title: L10n.loadOnVRoidHub.text, action: #selector(openVRoidHub)),
])
}

@objc private func loadVRM() {
Logger.log(event: .loadVRMFile)
Logger.log("")
guard let url = FileUtility.openFile(type: .vrm) else { return }
UniBridge.shared.loadVRM(url.path)
}

@objc private func openVRoidHub() {
Logger.log(event: .openVRoidHub)
Logger.log("")
UniBridge.shared.openVRoidHub()
}
}

// MARK: - Object
private extension AppMenu {
private func setupObjectMenu(subMenu: NSMenu) {
Self.makeSubMenu(menu: subMenu, title: L10n.object.text, items: [
makeMenuItem(title: L10n.resetAvatarPosition.text, action: #selector(resetAvatarPosition)),
.separator(),
makeMenuItem(title: L10n.addImage.text, action: #selector(addImage)),
makeMenuItem(title: L10n.addScreenCapture.text, action: #selector(addScreenCapture)),
makeMenuItem(title: L10n.addVideoCapture.text, action: #selector(addVideoCapture)),
makeMenuItem(title: L10n.addWeb.text, action: #selector(addWeb)),
.separator(),
makeMenuItem(title: L10n.addWind.text, action: #selector(addWind)),
])
}

@objc private func resetAvatarPosition() {
UniBridge.shared.resetCamera()
}

@objc private func addImage() {
guard let url = FileUtility.openFile(type: .image) else { return }
SceneObjectManager.shared.addImage(url: url)
}

@objc private func addScreenCapture() {
showScreenRecorderPreferenceView { recorder in
guard let config = recorder.captureConfig, let screenId = config.id else { return }
let id = RenderTextureManager.shared.add(recorder)
SceneObjectManager.shared.add(.init(id: id, type: .screen(.init(id: screenId, captureType: config.captureType.type, textureSize: recorder.size, crop: recorder.cropRect, filter: nil)), isHidden: false, isLocked: false))
}
}

@objc private func addVideoCapture() {
CaptureDeviceRenderer.selectDevice { drawer in
let id = RenderTextureManager.shared.add(drawer)
SceneObjectManager.shared.add(.init(id: id, type: .videoCapture(.init(id: drawer.id, textureSize: drawer.size, crop: drawer.cropRect, filter: nil)), isHidden: false, isLocked: false))
}
}

@objc private func addWeb() {
WebRenderer.showPreferencesForAdding()
}

@objc private func addWind() {
SceneObjectManager.shared.add(.init(type: .wind(), isHidden: false, isLocked: false))
}
}

// MARK: - Avatar
private extension AppMenu {
private func setupAvatarMenu(subMenu: NSMenu) {
Self.makeSubMenu(menu: subMenu, title: L10n.avatar.text, items: [
makeMenuItem(title: L10n.editAvatar.text, action: #selector(editAvatar)),
.separator(),
makeMenuItem(title: L10n.calibrate.text, action: #selector(resetCalibration)),
])
}

@objc private func editAvatar() {
Logger.log("")
UniBridge.shared.editAvatar()
}

@objc private func resetCalibration() {
Tracking.shared.resetCalibration()
}
}

// MARK: - Window
private extension AppMenu {
private func setupWindowMenu(subMenu: NSMenu) {
let alwaysOnTop = makeMenuItem(title: L10n.alwaysOnTop.text, action: #selector(toggleAlwaysOnTop))
alwaysOnTop.state = UserDefaults.standard.value(for: .alwaysOnTopEnabled) ? .on : .off
Self.makeSubMenu(menu: subMenu, title: L10n.window.text, items: [
alwaysOnTop,
])
}

@objc private func toggleAlwaysOnTop(_ sender: NSMenuItem) {
let enabled = !UserDefaults.standard.value(for: .alwaysOnTopEnabled)
VCamSystem.shared.windowManager.setAlwaysOnTopEnabled(enabled)
sender.state = enabled ? .on : .off
}
}

// MARK: - Help
private extension AppMenu {
private func setupHelpMenu(subMenu: NSMenu) {
Self.makeSubMenu(menu: subMenu, title: L10n.help.text, items: [
makeMenuItem(title: L10n.anyProblem.text, action: #selector(help)),
])
}

@objc private func help() {
let url = URL(string: "https://tattn.gitbook.io/vcam/sono-others/sono/faq")!
NSWorkspace.shared.open(url)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public struct VCamSettingGeneralView: View {
.frame(maxWidth: .infinity, alignment: .leading)
}
.onChange(of: useAddToMacOSMenuBar) { newValue in
WindowManager.shared.isMacOSMenubarVisible = newValue
VCamSystem.shared.windowManager.isMacOSMenubarVisible = newValue
}

GroupBox {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ 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
Expand Down Expand Up @@ -59,7 +58,6 @@ public struct VCamSettingTrackingView: View {

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))
}
Expand Down
Loading

0 comments on commit 30b641d

Please sign in to comment.