diff --git a/Pomelo/Emulation/ControllerView/Gyro/MotionManager.swift b/Pomelo/Emulation/ControllerView/Gyro/MotionManager.swift new file mode 100644 index 0000000..7bb88d6 --- /dev/null +++ b/Pomelo/Emulation/ControllerView/Gyro/MotionManager.swift @@ -0,0 +1,44 @@ +import CoreMotion +import SwiftUI + +class MotionManager: ObservableObject { + private var motionManager = CMMotionManager() + @Published var gyroData: CMGyroData? + @Published var accelerometerData: CMAccelerometerData? + + init() { + startGyroUpdates() + startAccelerometerUpdates() + } + + func startGyroUpdates() { + if motionManager.isGyroAvailable { + motionManager.gyroUpdateInterval = 1.0 / 60.0 // Update at 60 Hz + motionManager.startGyroUpdates(to: .main) { [weak self] data, error in + if let error = error { + print("Gyro Error: \(error.localizedDescription)") + return + } + self?.gyroData = data + } + } + } + + func startAccelerometerUpdates() { + if motionManager.isAccelerometerAvailable { + motionManager.accelerometerUpdateInterval = 1.0 / 60.0 // Update at 60 Hz + motionManager.startAccelerometerUpdates(to: .main) { [weak self] data, error in + if let error = error { + print("Accelerometer Error: \(error.localizedDescription)") + return + } + self?.accelerometerData = data + } + } + } + + deinit { + motionManager.stopGyroUpdates() + motionManager.stopAccelerometerUpdates() + } +} diff --git a/Pomelo/Extentions/MTLViewExtentions.swift b/Pomelo/Extentions/MTLViewExtentions.swift new file mode 100644 index 0000000..e69de29 diff --git a/Pomelo/LibraryViews/GameGrid/GameGridView.swift b/Pomelo/LibraryViews/GameGrid/GameGridView.swift new file mode 100644 index 0000000..e668308 --- /dev/null +++ b/Pomelo/LibraryViews/GameGrid/GameGridView.swift @@ -0,0 +1,88 @@ +// +// GameListView.swift +// Pomelo +// +// Created by Stossy11 on 9/10/2024. +// Copyright © 2024 Stossy11. All rights reserved. +// + + +struct GameListView: View { + @State var core: Core + @State private var searchText = "" + @State var game: Int = 1 + @State var startgame: Bool = false + @State var showAlert = false + @State var selectedGame: PomeloGame? + @State var alertMessage: Alert? = nil + + var body: some View { + let filteredGames = core.games.filter { game in + guard let PomeloGame = game as? PomeloGame else { return false } + return searchText.isEmpty || PomeloGame.title.localizedCaseInsensitiveContains(searchText) + } + + ScrollView { + VStack { + VStack(alignment: .leading) { + LazyVGrid(columns: [GridItem(.adaptive(minimum: 200))], spacing: 2) { + ForEach(0.. { + Timer.publish(every: 60, on: .main, in: .common).autoconnect() // Update every minute + } + + var body: some View { + let hour = Calendar.current.component(.hour, from: currentDate) + let minutes = Calendar.current.component(.minute, from: currentDate) + + HStack { + Image(systemName: "person.crop.circle.fill") + .resizable() + .frame(width: 40, height: 40) + + Spacer() + + Text("\(hour % 12 == 0 ? 12 : hour % 12):\(String(format: "%02d", minutes)) \(hour >= 12 ? "PM" : "AM")") + // .foregroundColor(.black) + .font(.system(size: 22)) + + Spacer() + + HStack { + Image(systemName: "wifi") + Image(systemName: batteryImageName(for: batteryLevel)) + } + } + .padding(.horizontal, 20) + .padding(.vertical, 10) + .onReceive(timer) { _ in + currentDate = Date() + } + .onAppear { + UIDevice.current.isBatteryMonitoringEnabled = true + batteryLevel = UIDevice.current.batteryLevel + batteryState = UIDevice.current.batteryState + + + + // Add observers for battery level and state changes + NotificationCenter.default.addObserver( + forName: UIDevice.batteryLevelDidChangeNotification, + object: nil, + queue: .main) { _ in + self.batteryLevel = UIDevice.current.batteryLevel + } + + NotificationCenter.default.addObserver( + forName: UIDevice.batteryStateDidChangeNotification, + object: nil, + queue: .main) { _ in + self.batteryState = UIDevice.current.batteryState + } + } + .onDisappear { + // Remove observers when the view disappears + NotificationCenter.default.removeObserver(self) + } + } + + private func batteryImageName(for level: Float) -> String { + switch level { + case 0.0: return "battery.0" + case 0.1..<0.25: return "battery.25" + case 0.25..<0.5: return "battery.50" + case 0.5..<0.75: return "battery.75" + case 0.75..<1.0: return "battery.75" + default: return "battery.100" + } + } +} + diff --git a/Pomelo/NavigationManager/NavView.swift b/Pomelo/NavigationManager/NavView.swift deleted file mode 100644 index e67ecde..0000000 --- a/Pomelo/NavigationManager/NavView.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// NavView.swift -// Pomelo -// -// Created by Stossy11 on 14/7/2024. -// - -import SwiftUI -import Sudachi - -struct NavView: View { - @Binding var core: Core - @State private var selectedTab = 0 - - var body: some View { - TabView(selection: $selectedTab) { - LibraryView(core: $core) - .tabItem { - Label("Library", systemImage: "rectangle.on.rectangle") - } - .tag(0) - BootOSView(core: $core, currentnavigarion: $selectedTab) - .toolbar(.hidden, for: .tabBar) - .tabItem { - Label("Boot OS", systemImage: "house") - } - .tag(1) - SettingsView(core: core) - .tabItem { - Label("Settings", systemImage: "gear") - } - .tag(2) - } - } -} - diff --git a/Pomelo/ScreenshotManager/ScreenShotListView.swift b/Pomelo/ScreenshotManager/ScreenShotListView.swift new file mode 100644 index 0000000..e69de29 diff --git a/Pomelo/ScreenshotManager/Zoomable.swift b/Pomelo/ScreenshotManager/Zoomable.swift new file mode 100644 index 0000000..487a8a0 --- /dev/null +++ b/Pomelo/ScreenshotManager/Zoomable.swift @@ -0,0 +1,209 @@ +#if os(iOS) + +import SwiftUI + +struct ZoomableModifier: ViewModifier { + let minZoomScale: CGFloat + let doubleTapZoomScale: CGFloat + + @State private var lastTransform: CGAffineTransform = .identity + @State private var transform: CGAffineTransform = .identity + @State private var contentSize: CGSize = .zero + + func body(content: Content) -> some View { + content + .background(alignment: .topLeading) { + GeometryReader { proxy in + Color.clear + .onAppear { + contentSize = proxy.size + } + } + } + .animatableTransformEffect(transform) + .gesture(dragGesture, including: transform == .identity ? .none : .all) + .modify { view in + if #available(iOS 17.0, *) { + view.gesture(magnificationGesture) + } else { + view.gesture(oldMagnificationGesture) + } + } + .gesture(doubleTapGesture) + } + + @available(iOS, introduced: 16.0, deprecated: 17.0) + private var oldMagnificationGesture: some Gesture { + MagnificationGesture() + .onChanged { value in + let zoomFactor = 0.5 + let scale = value * zoomFactor + transform = lastTransform.scaledBy(x: scale, y: scale) + } + .onEnded { _ in + onEndGesture() + } + } + + @available(iOS 17.0, *) + private var magnificationGesture: some Gesture { + MagnifyGesture(minimumScaleDelta: 0) + .onChanged { value in + let newTransform = CGAffineTransform.anchoredScale( + scale: value.magnification, + anchor: value.startAnchor.scaledBy(contentSize) + ) + + withAnimation(.interactiveSpring) { + transform = lastTransform.concatenating(newTransform) + } + } + .onEnded { _ in + onEndGesture() + } + } + + private var doubleTapGesture: some Gesture { + SpatialTapGesture(count: 2) + .onEnded { value in + let newTransform: CGAffineTransform = + if transform.isIdentity { + .anchoredScale(scale: doubleTapZoomScale, anchor: value.location) + } else { + .identity + } + + withAnimation(.linear(duration: 0.15)) { + transform = newTransform + lastTransform = newTransform + } + } + } + + private var dragGesture: some Gesture { + DragGesture() + .onChanged { value in + withAnimation(.interactiveSpring) { + transform = lastTransform.translatedBy( + x: value.translation.width / transform.scaleX, + y: value.translation.height / transform.scaleY + ) + } + } + .onEnded { _ in + onEndGesture() + } + } + + private func onEndGesture() { + let newTransform = limitTransform(transform) + + withAnimation(.snappy(duration: 0.1)) { + transform = newTransform + lastTransform = newTransform + } + } + + private func limitTransform(_ transform: CGAffineTransform) -> CGAffineTransform { + let scaleX = transform.scaleX + let scaleY = transform.scaleY + + if scaleX < minZoomScale + || scaleY < minZoomScale + { + return .identity + } + + let maxX = contentSize.width * (scaleX - 1) + let maxY = contentSize.height * (scaleY - 1) + + if transform.tx > 0 + || transform.tx < -maxX + || transform.ty > 0 + || transform.ty < -maxY + { + let tx = min(max(transform.tx, -maxX), 0) + let ty = min(max(transform.ty, -maxY), 0) + var transform = transform + transform.tx = tx + transform.ty = ty + return transform + } + + return transform + } +} + +public extension View { + @ViewBuilder + func zoomable( + minZoomScale: CGFloat = 1, + doubleTapZoomScale: CGFloat = 3 + ) -> some View { + modifier(ZoomableModifier( + minZoomScale: minZoomScale, + doubleTapZoomScale: doubleTapZoomScale + )) + } + + @ViewBuilder + func zoomable( + minZoomScale: CGFloat = 1, + doubleTapZoomScale: CGFloat = 3, + outOfBoundsColor: Color = .clear + ) -> some View { + GeometryReader { proxy in + ZStack { + outOfBoundsColor + self.zoomable( + minZoomScale: minZoomScale, + doubleTapZoomScale: doubleTapZoomScale + ) + } + } + } +} + +private extension View { + @ViewBuilder + func modify(@ViewBuilder _ fn: (Self) -> some View) -> some View { + fn(self) + } + + @ViewBuilder + func animatableTransformEffect(_ transform: CGAffineTransform) -> some View { + scaleEffect( + x: transform.scaleX, + y: transform.scaleY, + anchor: .zero + ) + .offset(x: transform.tx, y: transform.ty) + } +} + +private extension UnitPoint { + func scaledBy(_ size: CGSize) -> CGPoint { + .init( + x: x * size.width, + y: y * size.height + ) + } +} + +private extension CGAffineTransform { + static func anchoredScale(scale: CGFloat, anchor: CGPoint) -> CGAffineTransform { + CGAffineTransform(translationX: anchor.x, y: anchor.y) + .scaledBy(x: scale, y: scale) + .translatedBy(x: -anchor.x, y: -anchor.y) + } + + var scaleX: CGFloat { + sqrt(a * a + c * c) + } + + var scaleY: CGFloat { + sqrt(b * b + d * d) + } +} + +#endif