diff --git a/Package.swift b/Package.swift index 6723e0c..a3098cb 100644 --- a/Package.swift +++ b/Package.swift @@ -20,8 +20,8 @@ let package = Package( targets: [ .binaryTarget( name: "OpenMultitouchSupportXCF", - url: "https://github.com/Kyome22/OpenMultitouchSupport/releases/download/3.0.3/OpenMultitouchSupportXCF.xcframework.zip", - checksum: "9ffe72a65f0107e87a003485ab9530e772a6b45953af2f7d0cc41665d1873dea" + url: "https://github.com/KrishKrosh/OpenMultitouchSupport/releases/download/v1.0.0/OpenMultitouchSupportXCF.xcframework.zip", + checksum: "dbee657fea27f427e5b05c89cfa923f9c1c733b1ac6a6cb71360cd77c93f08ca" ), .target( name: "OpenMultitouchSupport", diff --git a/Sources/OpenMultitouchSupport/OMSManager.swift b/Sources/OpenMultitouchSupport/OMSManager.swift index 1bf92e2..691a566 100644 --- a/Sources/OpenMultitouchSupport/OMSManager.swift +++ b/Sources/OpenMultitouchSupport/OMSManager.swift @@ -5,9 +5,23 @@ */ import Combine -import OpenMultitouchSupportXCF +@preconcurrency import OpenMultitouchSupportXCF import os +public struct OMSDeviceInfo: Sendable, Hashable { + public let deviceName: String + public let deviceID: String + public let isBuiltIn: Bool + internal nonisolated(unsafe) let deviceInfo: OpenMTDeviceInfo + + internal init(_ deviceInfo: OpenMTDeviceInfo) { + self.deviceInfo = deviceInfo + self.deviceName = deviceInfo.deviceName + self.deviceID = deviceInfo.deviceID + self.isBuiltIn = deviceInfo.isBuiltIn + } +} + public final class OMSManager: Sendable { public static let shared = OMSManager() @@ -30,6 +44,17 @@ public final class OMSManager: Sendable { public var isListening: Bool { protectedListener.withLockUnchecked { $0 != nil } } + + public var availableDevices: [OMSDeviceInfo] { + guard let xcfManager = protectedManager.withLockUnchecked(\.self) else { return [] } + return xcfManager.availableDevices().map { OMSDeviceInfo($0) } + } + + public var currentDevice: OMSDeviceInfo? { + guard let xcfManager = protectedManager.withLockUnchecked(\.self), + let current = xcfManager.currentDevice() else { return nil } + return OMSDeviceInfo(current) + } private init() { protectedManager = .init(uncheckedState: OpenMTManager.shared()) @@ -60,6 +85,12 @@ public final class OMSManager: Sendable { protectedListener.withLockUnchecked { $0 = nil } return true } + + @discardableResult + public func selectDevice(_ device: OMSDeviceInfo) -> Bool { + guard let xcfManager = protectedManager.withLockUnchecked(\.self) else { return false } + return xcfManager.selectDevice(device.deviceInfo) + } @objc func listen(_ event: OpenMTEvent) { guard let touches = (event.touches as NSArray) as? [OpenMTTouch] else { return } diff --git a/TrackWeight.xcodeproj/project.pbxproj b/TrackWeight.xcodeproj/project.pbxproj index 4513e0d..b909043 100644 --- a/TrackWeight.xcodeproj/project.pbxproj +++ b/TrackWeight.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ 77292AA22B931E04001CA3F6 /* DebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77292AA32B931E04001CA3F6 /* DebugView.swift */; }; 77292AA42B931E05001CA3F6 /* ScaleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77292AA52B931E05001CA3F6 /* ScaleView.swift */; }; 77292AA62B931E06001CA3F6 /* ScaleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77292AA72B931E06001CA3F6 /* ScaleViewModel.swift */; }; + 93A095122E33359600E1E1D1 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A095112E33359600E1E1D1 /* SettingsView.swift */; }; 93ABD0212E2E01E200668D4F /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93ABD0202E2E01E200668D4F /* HomeView.swift */; }; /* End PBXBuildFile section */ @@ -37,6 +38,7 @@ 77292AA32B931E04001CA3F6 /* DebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugView.swift; sourceTree = ""; }; 77292AA52B931E05001CA3F6 /* ScaleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaleView.swift; sourceTree = ""; }; 77292AA72B931E06001CA3F6 /* ScaleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaleViewModel.swift; sourceTree = ""; }; + 93A095112E33359600E1E1D1 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 93ABD0202E2E01E200668D4F /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -81,6 +83,7 @@ 77292A9D2B931E01001CA3F6 /* WeighingState.swift */, 93ABD0202E2E01E200668D4F /* HomeView.swift */, 77292A9F2B931E02001CA3F6 /* WeighingViewModel.swift */, + 93A095112E33359600E1E1D1 /* SettingsView.swift */, 77292AA12B931E03001CA3F6 /* TrackWeightView.swift */, 77292AA32B931E04001CA3F6 /* DebugView.swift */, 77292AA52B931E05001CA3F6 /* ScaleView.swift */, @@ -188,6 +191,7 @@ 77292AA22B931E04001CA3F6 /* DebugView.swift in Sources */, 77292AA42B931E05001CA3F6 /* ScaleView.swift in Sources */, 77292AA62B931E06001CA3F6 /* ScaleViewModel.swift in Sources */, + 93A095122E33359600E1E1D1 /* SettingsView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/TrackWeight/ContentView.swift b/TrackWeight/ContentView.swift index 5f4ee26..7a4bfa7 100644 --- a/TrackWeight/ContentView.swift +++ b/TrackWeight/ContentView.swift @@ -31,10 +31,10 @@ struct ContentView: View { } .tag(1) - DebugView() + SettingsView() .tabItem { - Image(systemName: "hand.point.up.left") - Text("Debug") + Image(systemName: "gearshape") + Text("Settings") } .tag(2) } diff --git a/TrackWeight/ContentViewModel.swift b/TrackWeight/ContentViewModel.swift index 254739d..6d22d1d 100644 --- a/TrackWeight/ContentViewModel.swift +++ b/TrackWeight/ContentViewModel.swift @@ -1,6 +1,6 @@ // // ContentViewModel.swift -// TrackWeight +// OMSDemo // // Created by Takuto Nakamura on 2024/03/02. // @@ -12,16 +12,19 @@ import SwiftUI final class ContentViewModel: ObservableObject { @Published var touchData = [OMSTouchData]() @Published var isListening: Bool = false + @Published var availableDevices = [OMSDeviceInfo]() + @Published var selectedDevice: OMSDeviceInfo? private let manager = OMSManager.shared private var task: Task? - init() {} + init() { + loadDevices() + } func onAppear() { task = Task { [weak self, manager] in for await touchData in manager.touchDataStream { - print(touchData) await MainActor.run { self?.touchData = touchData } @@ -45,4 +48,15 @@ final class ContentViewModel: ObservableObject { isListening = false } } + + func loadDevices() { + availableDevices = manager.availableDevices + selectedDevice = manager.currentDevice + } + + func selectDevice(_ device: OMSDeviceInfo) { + if manager.selectDevice(device) { + selectedDevice = device + } + } } diff --git a/TrackWeight/DebugView.swift b/TrackWeight/DebugView.swift index 5d626ee..c0c404d 100644 --- a/TrackWeight/DebugView.swift +++ b/TrackWeight/DebugView.swift @@ -13,6 +13,30 @@ struct DebugView: View { var body: some View { VStack { + + // Device Selector + if !viewModel.availableDevices.isEmpty { + VStack(alignment: .leading) { + Text("Trackpad Device:") + .font(.headline) + Picker("Select Device", selection: Binding( + get: { viewModel.selectedDevice }, + set: { device in + if let device = device { + viewModel.selectDevice(device) + } + } + )) { + ForEach(viewModel.availableDevices, id: \.self) { device in + Text("\(device.deviceName) (ID: \(device.deviceID))") + .tag(device as OMSDeviceInfo?) + } + } + .pickerStyle(MenuPickerStyle()) + } + .padding(.bottom) + } + if viewModel.isListening { Button { viewModel.stop() diff --git a/TrackWeight/SettingsView.swift b/TrackWeight/SettingsView.swift new file mode 100644 index 0000000..67e7a25 --- /dev/null +++ b/TrackWeight/SettingsView.swift @@ -0,0 +1,147 @@ +// +// SettingsView.swift +// TrackWeight +// + +import OpenMultitouchSupport +import SwiftUI + +struct SettingsView: View { + @StateObject private var viewModel = ContentViewModel() + @State private var showDebugView = false + + var body: some View { + VStack(spacing: 0) { + // Minimal Header + Text("Settings") + .font(.title) + .fontWeight(.medium) + .padding(.top, 32) + .padding(.bottom, 32) + + // Settings Cards + VStack(spacing: 20) { + // Device Card + SettingsCard { + VStack(spacing: 20) { + // Status Row + HStack { + HStack(spacing: 12) { + Text("Trackpad") + .font(.headline) + .fontWeight(.medium) + } + + Spacer() + + if !viewModel.availableDevices.isEmpty { + Text("\(viewModel.availableDevices.count) device\(viewModel.availableDevices.count == 1 ? "" : "s")") + .font(.caption) + .foregroundColor(.secondary) + } + } + + // Device Selector + if !viewModel.availableDevices.isEmpty { + HStack { + Picker("", selection: Binding( + get: { viewModel.selectedDevice }, + set: { device in + if let device = device { + viewModel.selectDevice(device) + } + } + )) { + ForEach(viewModel.availableDevices, id: \.self) { device in + Text(device.deviceName) + .tag(device as OMSDeviceInfo?) + } + } + .pickerStyle(MenuPickerStyle()) + + Spacer() + } + } else { + HStack { + Text("No devices available") + .foregroundColor(.secondary) + Spacer() + } + } + } + } + + // Debug Card + SettingsCard { + Button(action: { showDebugView = true }) { + HStack(spacing: 16) { + VStack(alignment: .leading, spacing: 4) { + Text("Debug Console") + .font(.headline) + .fontWeight(.medium) + .foregroundColor(.primary) + + Text("Raw touch data & diagnostics") + .font(.caption) + .foregroundColor(.secondary) + } + + Spacer() + + Image(systemName: "chevron.right") + .font(.caption) + .fontWeight(.medium) + .foregroundColor(.secondary.opacity(0.6)) + } + .contentShape(Rectangle()) + } + .buttonStyle(CardButtonStyle()) + } + } + .frame(maxWidth: 480) + .padding(.horizontal, 40) + + Spacer() + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color(NSColor.windowBackgroundColor)) + .sheet(isPresented: $showDebugView) { + DebugView() + .frame(minWidth: 700, minHeight: 500) + } + .onAppear { + viewModel.loadDevices() + } + } +} + +struct SettingsCard: View { + let content: Content + + init(@ViewBuilder content: () -> Content) { + self.content = content() + } + + var body: some View { + VStack { + content + } + .padding(24) + .background(Color(NSColor.controlBackgroundColor)) + .cornerRadius(16) + .shadow(color: Color.black.opacity(0.03), radius: 1, x: 0, y: 1) + .shadow(color: Color.black.opacity(0.05), radius: 8, x: 0, y: 4) + } +} + +struct CardButtonStyle: ButtonStyle { + func makeBody(configuration: Configuration) -> some View { + configuration.label + .scaleEffect(configuration.isPressed ? 0.98 : 1.0) + .animation(.easeInOut(duration: 0.1), value: configuration.isPressed) + } +} + +#Preview { + SettingsView() +} \ No newline at end of file