From 1c5d3166a885a24b9c3fd156ebf7eb32da09ede2 Mon Sep 17 00:00:00 2001 From: KrishKrosh Date: Thu, 24 Jul 2025 23:43:10 -0400 Subject: [PATCH 1/4] Update OpenMultitouchSupport dependency and enhance OMSManager with device management features --- Package.swift | 4 +-- .../OpenMultitouchSupport/OMSManager.swift | 33 ++++++++++++++++++- TrackWeight/ContentViewModel.swift | 20 +++++++++-- TrackWeight/DebugView.swift | 24 ++++++++++++++ 4 files changed, 75 insertions(+), 6 deletions(-) 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/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() From 17a75ba958b8178fdb7bff632d59c0ee9649e0da Mon Sep 17 00:00:00 2001 From: KrishKrosh Date: Thu, 24 Jul 2025 23:49:49 -0400 Subject: [PATCH 2/4] add settings page --- TrackWeight.xcodeproj/project.pbxproj | 4 + TrackWeight/ContentView.swift | 6 +- TrackWeight/SettingsView.swift | 186 ++++++++++++++++++++++++++ 3 files changed, 193 insertions(+), 3 deletions(-) create mode 100644 TrackWeight/SettingsView.swift 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/SettingsView.swift b/TrackWeight/SettingsView.swift new file mode 100644 index 0000000..40eefdc --- /dev/null +++ b/TrackWeight/SettingsView.swift @@ -0,0 +1,186 @@ +// +// SettingsView.swift +// TrackWeight +// + +import OpenMultitouchSupport +import SwiftUI + +struct SettingsView: View { + @StateObject private var viewModel = ContentViewModel() + @State private var showDebugView = false + + var body: some View { + ScrollView { + VStack(spacing: 0) { + // Header + VStack(spacing: 8) { + Image(systemName: "gearshape.fill") + .font(.system(size: 60)) + .foregroundColor(.blue) + + Text("Settings") + .font(.largeTitle) + .fontWeight(.bold) + } + .padding(.top, 40) + .padding(.bottom, 40) + + // Settings Content + VStack(spacing: 30) { + // Device Settings Section + SettingsSection(title: "Device", icon: "trackpad") { + VStack(alignment: .leading, spacing: 16) { + if !viewModel.availableDevices.isEmpty { + VStack(alignment: .leading, spacing: 8) { + Text("Trackpad Device") + .font(.headline) + .foregroundColor(.primary) + + Text("Select which trackpad device to use for weight tracking") + .font(.caption) + .foregroundColor(.secondary) + + Picker("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()) + .frame(maxWidth: .infinity, alignment: .leading) + } + } else { + VStack(alignment: .leading, spacing: 8) { + Text("No Trackpad Devices Found") + .font(.headline) + .foregroundColor(.primary) + + Text("Make sure your trackpad is connected and try restarting the app") + .font(.caption) + .foregroundColor(.secondary) + } + } + } + } + + // Advanced Settings Section + SettingsSection(title: "Advanced", icon: "gear") { + VStack(spacing: 12) { + SettingsRow( + icon: "hand.point.up.left", + title: "Debug View", + subtitle: "View raw touch data and device information", + action: { + showDebugView = true + } + ) + } + } + } + .frame(maxWidth: 600) + .padding(.horizontal, 40) + .padding(.bottom, 40) + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color(NSColor.windowBackgroundColor)) + .sheet(isPresented: $showDebugView) { + DebugView() + .frame(minWidth: 700, minHeight: 500) + } + .onAppear { + viewModel.loadDevices() + } + } +} + +struct SettingsSection: View { + let title: String + let icon: String + let content: Content + + init(title: String, icon: String, @ViewBuilder content: () -> Content) { + self.title = title + self.icon = icon + self.content = content() + } + + var body: some View { + VStack(alignment: .leading, spacing: 16) { + HStack(spacing: 12) { + Image(systemName: icon) + .font(.title2) + .foregroundColor(.blue) + + Text(title) + .font(.title2) + .fontWeight(.semibold) + } + + VStack(alignment: .leading, spacing: 12) { + content + } + .padding(24) + .background(Color(NSColor.controlBackgroundColor)) + .cornerRadius(12) + } + } +} + +struct SettingsRow: View { + let icon: String + let title: String + let subtitle: String + let action: () -> Void + + @State private var isHovered = false + + var body: some View { + Button(action: action) { + HStack(spacing: 16) { + Image(systemName: icon) + .font(.title3) + .foregroundColor(.blue) + .frame(width: 24) + + VStack(alignment: .leading, spacing: 2) { + Text(title) + .font(.headline) + .foregroundColor(.primary) + + Text(subtitle) + .font(.caption) + .foregroundColor(.secondary) + } + + Spacer() + + Image(systemName: "chevron.right") + .font(.caption) + .foregroundColor(.secondary) + } + .padding(.vertical, 8) + .padding(.horizontal, 12) + .background(isHovered ? Color(NSColor.selectedControlColor).opacity(0.1) : Color.clear) + .cornerRadius(8) + } + .buttonStyle(PlainButtonStyle()) + .onHover { hovered in + withAnimation(.easeInOut(duration: 0.2)) { + isHovered = hovered + } + } + } +} + +#Preview { + SettingsView() +} \ No newline at end of file From be6d1c0208c2aac47403907f07afc21d9d50e86a Mon Sep 17 00:00:00 2001 From: KrishKrosh Date: Thu, 24 Jul 2025 23:55:56 -0400 Subject: [PATCH 3/4] less cluttered --- TrackWeight/SettingsView.swift | 39 +++++++++------------------------- 1 file changed, 10 insertions(+), 29 deletions(-) diff --git a/TrackWeight/SettingsView.swift b/TrackWeight/SettingsView.swift index 40eefdc..795eeb9 100644 --- a/TrackWeight/SettingsView.swift +++ b/TrackWeight/SettingsView.swift @@ -15,10 +15,6 @@ struct SettingsView: View { VStack(spacing: 0) { // Header VStack(spacing: 8) { - Image(systemName: "gearshape.fill") - .font(.system(size: 60)) - .foregroundColor(.blue) - Text("Settings") .font(.largeTitle) .fontWeight(.bold) @@ -29,11 +25,11 @@ struct SettingsView: View { // Settings Content VStack(spacing: 30) { // Device Settings Section - SettingsSection(title: "Device", icon: "trackpad") { + SettingsSection(title: "Device") { VStack(alignment: .leading, spacing: 16) { if !viewModel.availableDevices.isEmpty { VStack(alignment: .leading, spacing: 8) { - Text("Trackpad Device") + Text("Trackpad") .font(.headline) .foregroundColor(.primary) @@ -41,7 +37,7 @@ struct SettingsView: View { .font(.caption) .foregroundColor(.secondary) - Picker("Device", selection: Binding( + Picker("", selection: Binding( get: { viewModel.selectedDevice }, set: { device in if let device = device { @@ -59,9 +55,9 @@ struct SettingsView: View { } } else { VStack(alignment: .leading, spacing: 8) { - Text("No Trackpad Devices Found") + Text("No devices found") .font(.headline) - .foregroundColor(.primary) + .foregroundColor(.secondary) Text("Make sure your trackpad is connected and try restarting the app") .font(.caption) @@ -72,10 +68,9 @@ struct SettingsView: View { } // Advanced Settings Section - SettingsSection(title: "Advanced", icon: "gear") { + SettingsSection(title: "Advanced") { VStack(spacing: 12) { SettingsRow( - icon: "hand.point.up.left", title: "Debug View", subtitle: "View raw touch data and device information", action: { @@ -104,26 +99,18 @@ struct SettingsView: View { struct SettingsSection: View { let title: String - let icon: String let content: Content - init(title: String, icon: String, @ViewBuilder content: () -> Content) { + init(title: String, @ViewBuilder content: () -> Content) { self.title = title - self.icon = icon self.content = content() } var body: some View { VStack(alignment: .leading, spacing: 16) { - HStack(spacing: 12) { - Image(systemName: icon) - .font(.title2) - .foregroundColor(.blue) - - Text(title) - .font(.title2) - .fontWeight(.semibold) - } + Text(title) + .font(.title2) + .fontWeight(.semibold) VStack(alignment: .leading, spacing: 12) { content @@ -136,7 +123,6 @@ struct SettingsSection: View { } struct SettingsRow: View { - let icon: String let title: String let subtitle: String let action: () -> Void @@ -146,11 +132,6 @@ struct SettingsRow: View { var body: some View { Button(action: action) { HStack(spacing: 16) { - Image(systemName: icon) - .font(.title3) - .foregroundColor(.blue) - .frame(width: 24) - VStack(alignment: .leading, spacing: 2) { Text(title) .font(.headline) From d889adf6237f691a111f0779c6b590848462fbc0 Mon Sep 17 00:00:00 2001 From: KrishKrosh Date: Fri, 25 Jul 2025 00:00:21 -0400 Subject: [PATCH 4/4] better settings design --- TrackWeight/SettingsView.swift | 208 +++++++++++++++------------------ 1 file changed, 94 insertions(+), 114 deletions(-) diff --git a/TrackWeight/SettingsView.swift b/TrackWeight/SettingsView.swift index 795eeb9..67e7a25 100644 --- a/TrackWeight/SettingsView.swift +++ b/TrackWeight/SettingsView.swift @@ -11,79 +11,97 @@ struct SettingsView: View { @State private var showDebugView = false var body: some View { - ScrollView { - VStack(spacing: 0) { - // Header - VStack(spacing: 8) { - Text("Settings") - .font(.largeTitle) - .fontWeight(.bold) - } - .padding(.top, 40) - .padding(.bottom, 40) - - // Settings Content - VStack(spacing: 30) { - // Device Settings Section - SettingsSection(title: "Device") { - VStack(alignment: .leading, spacing: 16) { + 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 { - VStack(alignment: .leading, spacing: 8) { - Text("Trackpad") - .font(.headline) - .foregroundColor(.primary) - - Text("Select which trackpad device to use for weight tracking") - .font(.caption) - .foregroundColor(.secondary) - - 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) (ID: \(device.deviceID))") - .tag(device as OMSDeviceInfo?) + 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) } } - .pickerStyle(MenuPickerStyle()) - .frame(maxWidth: .infinity, alignment: .leading) - } - } else { - VStack(alignment: .leading, spacing: 8) { - Text("No devices found") - .font(.headline) - .foregroundColor(.secondary) - - Text("Make sure your trackpad is connected and try restarting the app") - .font(.caption) - .foregroundColor(.secondary) + )) { + 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() } } } - - // Advanced Settings Section - SettingsSection(title: "Advanced") { - VStack(spacing: 12) { - SettingsRow( - title: "Debug View", - subtitle: "View raw touch data and device information", - action: { - showDebugView = true - } - ) + } + + // 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: 600) - .padding(.horizontal, 40) - .padding(.bottom, 40) } + .frame(maxWidth: 480) + .padding(.horizontal, 40) + + Spacer() } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color(NSColor.windowBackgroundColor)) @@ -97,68 +115,30 @@ struct SettingsView: View { } } -struct SettingsSection: View { - let title: String +struct SettingsCard: View { let content: Content - init(title: String, @ViewBuilder content: () -> Content) { - self.title = title + init(@ViewBuilder content: () -> Content) { self.content = content() } var body: some View { - VStack(alignment: .leading, spacing: 16) { - Text(title) - .font(.title2) - .fontWeight(.semibold) - - VStack(alignment: .leading, spacing: 12) { - content - } - .padding(24) - .background(Color(NSColor.controlBackgroundColor)) - .cornerRadius(12) + 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 SettingsRow: View { - let title: String - let subtitle: String - let action: () -> Void - - @State private var isHovered = false - - var body: some View { - Button(action: action) { - HStack(spacing: 16) { - VStack(alignment: .leading, spacing: 2) { - Text(title) - .font(.headline) - .foregroundColor(.primary) - - Text(subtitle) - .font(.caption) - .foregroundColor(.secondary) - } - - Spacer() - - Image(systemName: "chevron.right") - .font(.caption) - .foregroundColor(.secondary) - } - .padding(.vertical, 8) - .padding(.horizontal, 12) - .background(isHovered ? Color(NSColor.selectedControlColor).opacity(0.1) : Color.clear) - .cornerRadius(8) - } - .buttonStyle(PlainButtonStyle()) - .onHover { hovered in - withAnimation(.easeInOut(duration: 0.2)) { - isHovered = hovered - } - } +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) } }