From 632f01f169c4d459dc4f881d7e847a3ec5b3d82a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesu=CC=81s=20A=2E=20A=CC=81lvarez?= Date: Sat, 10 Feb 2024 14:33:11 +0100 Subject: [PATCH] visionOS: native settings menu --- Mini vMac.xcodeproj/project.pbxproj | 4 + Mini vMac/EmulatorProtocol.h | 4 +- Mini vMac/Mini vMac-Bridging-Header.h | 1 + Mini vMac/SettingsMenu.swift | 148 ++++++++++++++++++++++++++ Mini vMac/VisionSupport.swift | 6 +- 5 files changed, 156 insertions(+), 7 deletions(-) create mode 100644 Mini vMac/SettingsMenu.swift diff --git a/Mini vMac.xcodeproj/project.pbxproj b/Mini vMac.xcodeproj/project.pbxproj index 31c484e..b1268fa 100644 --- a/Mini vMac.xcodeproj/project.pbxproj +++ b/Mini vMac.xcodeproj/project.pbxproj @@ -61,6 +61,7 @@ 28D3C6152B7681420079E915 /* VisionSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D3C6142B7681420079E915 /* VisionSupport.swift */; }; 28D3C6172B76B8970079E915 /* DefaultSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D3C6162B76B8970079E915 /* DefaultSceneDelegate.swift */; }; 28D3C61B2B7781700079E915 /* KeyboardSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D3C61A2B7781700079E915 /* KeyboardSceneDelegate.swift */; }; + 28D3C61D2B7795060079E915 /* SettingsMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D3C61C2B7795060079E915 /* SettingsMenu.swift */; }; 28D5A3FD1CD6868F001A33F6 /* TouchScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = 28D5A3FC1CD6868E001A33F6 /* TouchScreen.m */; }; 28E3B7DF251D0F13007C273F /* MOUSEMDV.c in Sources */ = {isa = PBXBuildFile; fileRef = 28E3B7CC251D0F12007C273F /* MOUSEMDV.c */; }; 28E3B7E0251D0F13007C273F /* MOUSEMDV.c in Sources */ = {isa = PBXBuildFile; fileRef = 28E3B7CC251D0F12007C273F /* MOUSEMDV.c */; }; @@ -289,6 +290,7 @@ 28D3C6142B7681420079E915 /* VisionSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisionSupport.swift; sourceTree = ""; }; 28D3C6162B76B8970079E915 /* DefaultSceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultSceneDelegate.swift; sourceTree = ""; }; 28D3C61A2B7781700079E915 /* KeyboardSceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardSceneDelegate.swift; sourceTree = ""; }; + 28D3C61C2B7795060079E915 /* SettingsMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsMenu.swift; sourceTree = ""; }; 28D5A3FB1CD6868E001A33F6 /* TouchScreen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TouchScreen.h; sourceTree = ""; }; 28D5A3FC1CD6868E001A33F6 /* TouchScreen.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TouchScreen.m; sourceTree = ""; }; 28E3B7CC251D0F12007C273F /* MOUSEMDV.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = MOUSEMDV.c; sourceTree = ""; }; @@ -619,6 +621,7 @@ 28F676C61CD15E0B00FC6FA6 /* ViewController.h */, 28F676C71CD15E0B00FC6FA6 /* ViewController.m */, 28D3C6142B7681420079E915 /* VisionSupport.swift */, + 28D3C61C2B7795060079E915 /* SettingsMenu.swift */, 28848B601CDE97D600B86C45 /* InsertDiskViewController.h */, 28848B611CDE97D600B86C45 /* InsertDiskViewController.m */, 28848B631CDE97E900B86C45 /* SettingsViewController.h */, @@ -1308,6 +1311,7 @@ 28BA89881CE73FBC00A98104 /* MNVMApplication.m in Sources */, 28F6B4CF1CF77099002D76D0 /* compat.m in Sources */, 28F6B4521CF07C48002D76D0 /* UIImage+DiskImageIcon.m in Sources */, + 28D3C61D2B7795060079E915 /* SettingsMenu.swift in Sources */, 28BA897F1CE7315400A98104 /* KBKeyboardLayout.m in Sources */, 28D3C61B2B7781700079E915 /* KeyboardSceneDelegate.swift in Sources */, 28848B621CDE97D600B86C45 /* InsertDiskViewController.m in Sources */, diff --git a/Mini vMac/EmulatorProtocol.h b/Mini vMac/EmulatorProtocol.h index cbe7b19..852662f 100644 --- a/Mini vMac/EmulatorProtocol.h +++ b/Mini vMac/EmulatorProtocol.h @@ -10,7 +10,7 @@ @import CoreGraphics; @import QuartzCore; -typedef enum : NSInteger { +typedef NS_ENUM(NSInteger, EmulatorSpeed) { EmulatorSpeedAllOut = -1, EmulatorSpeed1x = 0, EmulatorSpeed2x = 1, @@ -18,7 +18,7 @@ typedef enum : NSInteger { EmulatorSpeed8x = 3, EmulatorSpeed16x = 4, EmulatorSpeed32x = 5 -} EmulatorSpeed; +}; @protocol Emulator diff --git a/Mini vMac/Mini vMac-Bridging-Header.h b/Mini vMac/Mini vMac-Bridging-Header.h index 0a5bcae..966ec45 100644 --- a/Mini vMac/Mini vMac-Bridging-Header.h +++ b/Mini vMac/Mini vMac-Bridging-Header.h @@ -4,5 +4,6 @@ #import "ViewController.h" #import "AppDelegate.h" +#import "EmulatorProtocol.h" #import "KBKeyboardView.h" #import "KBKeyboardLayout.h" diff --git a/Mini vMac/SettingsMenu.swift b/Mini vMac/SettingsMenu.swift new file mode 100644 index 0000000..1d229e7 --- /dev/null +++ b/Mini vMac/SettingsMenu.swift @@ -0,0 +1,148 @@ +// +// SettingsMenu.swift +// Mini vMac +// +// Created by Jesús A. Álvarez on 2024-02-10. +// Copyright © 2024 namedfork. All rights reserved. +// + +import SwiftUI + + +struct SettingsMenu: View { + var body: some View { + Menu() { + Section("Settings") { + SpeedMenu() + MachineMenu() + DisplayScalingMenu() + } + } label: { + Image(systemName: "gear") + }.menuOrder(.fixed) + } +} + +struct SpeedMenu: View { + @AppStorage("speedValue") var currentSpeed: EmulatorSpeed = .speed1x + private var currentSpeedImage: String { + switch currentSpeed { + case .speed1x: + "tortoise" + case .speedAllOut: + "hare" + case .speed2x: + "2.square" + case .speed4x: + "4.square" + case .speed8x: + "8.square" + case .speed16x: + "16.square" + case .speed32x: + "32.square" + @unknown default: + "hare" + } + } + var body: some View { + Menu("Speed", systemImage:currentSpeedImage) { + SpeedButton(label: "1x", speed: .speed1x) + SpeedButton(label: "2x", speed: .speed2x) + SpeedButton(label: "4x", speed: .speed4x) + SpeedButton(label: "8x", speed: .speed8x) + SpeedButton(label: "16x", speed: .speed16x) + SpeedButton(label: "32x", speed: .speed32x) + SpeedButton(label: "Unlimited", speed: .speedAllOut) + }.menuOrder(.fixed) + } +} + +struct SpeedButton: View { + @AppStorage("speedValue") var currentSpeed: EmulatorSpeed = .speed1x + let label: LocalizedStringKey + let speed: EmulatorSpeed + var body: some View { + if currentSpeed == speed { + Button(label, systemImage: "checkmark") {} + } else { + Button { + currentSpeed = speed + } label: { + Text(label) + } + } + } +} + +struct MachineMenu: View { + @AppStorage("machine") var currentMachine: String = "Plus4M" + let bundles = AppDelegate.shared.emulatorBundles?.sorted(by: { $0.bundleIdentifier! < $1.bundleIdentifier! }) ?? [] + var body: some View { + Menu("Machine", systemImage: "desktopcomputer") { + ForEach(bundles, id: \.bundleIdentifier) { bundle in + MachineButton(bundle: bundle) + } + }.menuOrder(.fixed) + } +} + +struct MachineButton: View { + @AppStorage("machine") var currentMachine: String = "Plus4M" + var bundle: Bundle + var body: some View { + if currentMachine == bundle.name { + Label { + Text("\(bundle.displayName ?? bundle.name)\n\(bundle.getInfoString ?? "")") + } icon: { + Image(systemName: "checkmark") + } + } else { + Button { + if !AppDelegate.emulator.anyDiskInserted { + currentMachine = bundle.name + AppDelegate.shared.loadAndStartEmulator() + } + } label: { + Text("\(bundle.displayName ?? bundle.name)\n\(bundle.getInfoString ?? "")") + } + } + } +} + +fileprivate extension Bundle { + var name: String { ((self.bundlePath as NSString).lastPathComponent as NSString).deletingPathExtension } + var displayName: String? { self.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String } + var getInfoString: String? { self.object(forInfoDictionaryKey: "CFBundleGetInfoString") as? String } +} + +struct DisplayScalingMenu: View { + @AppStorage("screenFilter") var scalingFilter: CALayerContentsFilter = .nearest + static let filters = [ + DisplayScalingFilter(filter: .nearest, name: "Nearest"), + DisplayScalingFilter(filter: .linear, name: "Linear"), + DisplayScalingFilter(filter: .trilinear, name: "Trilinear"), + ] + var body: some View { + Menu("Display Scaling", systemImage: "rectangle.and.text.magnifyingglass") { + ForEach(DisplayScalingMenu.filters, id: \.filter) { filter in + if scalingFilter == filter.filter { + Label { + Text(filter.name) + } icon: { + Image(systemName: "checkmark") + } + } else { + Button(filter.name) { + scalingFilter = filter.filter + } + } + } + }.menuOrder(.fixed) + } +} + +struct DisplayScalingFilter { + let filter: CALayerContentsFilter + let name: String +} diff --git a/Mini vMac/VisionSupport.swift b/Mini vMac/VisionSupport.swift index 0d69379..dbd1324 100644 --- a/Mini vMac/VisionSupport.swift +++ b/Mini vMac/VisionSupport.swift @@ -20,11 +20,7 @@ extension ViewController { VStack { Spacer(minLength: 80.0) HStack { - Button(action: { - AppDelegate.shared.showSettings(self) - }, label: { - Image(systemName: "gear") - }).glassBackgroundEffect() + SettingsMenu().glassBackgroundEffect() Button(action: { AppDelegate.shared.showInsertDisk(self)