diff --git a/Packages/vChewing_MainAssembly/Sources/MainAssembly/IMEApp.swift b/Packages/vChewing_MainAssembly/Sources/MainAssembly/IMEApp.swift index 6af55774..20b5f461 100644 --- a/Packages/vChewing_MainAssembly/Sources/MainAssembly/IMEApp.swift +++ b/Packages/vChewing_MainAssembly/Sources/MainAssembly/IMEApp.swift @@ -16,6 +16,21 @@ public enum IMEApp { // MARK: - 獲取輸入法的版本以及建置編號 public static let appVersionLabel: String = { + [appMainVersionLabel.joined(separator: " Build "), appSignedDateLabel].joined(separator: " - ") + }() + + public static let appMainVersionLabel: [String] = { + guard + let intBuild = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] as? String, + let strVer = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String + else { + return ["1.14.514", "19190810"] + } + + return [strVer, intBuild] + }() + + public static let appSignedDateLabel: String = { let maybeDateModified: Date? = { guard let executableURL = Bundle.main.executableURL, let infoDate = (try? executableURL.resourceValues(forKeys: [.contentModificationDateKey]))?.contentModificationDate @@ -25,32 +40,13 @@ public enum IMEApp { return infoDate }() - func dateStringTag(date givenDate: Date) -> String { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyyMMdd.HHmm" - dateFormatter.timeZone = .init(secondsFromGMT: +28800) ?? .current - let strDate = dateFormatter.string(from: givenDate) - return strDate - } - - guard - let intBuild = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] as? String, - let strVer = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String - else { - return "1.14.514 - 19190810" - } - - var theResults = ["\(strVer) Build \(intBuild)"] - if let theDate = Bundle.main.getCodeSignedDate() { - theResults.append(dateStringTag(date: theDate)) + return theDate.stringTag } else if let theDate = maybeDateModified { - theResults.append("\(dateStringTag(date: theDate)) Unsigned") + return "\(theDate.stringTag) Unsigned" } else { - theResults.append("Unsigned") + return "Unsigned" } - - return theResults.joined(separator: " - ") }() // MARK: - 輸入法的當前的簡繁體中文模式 @@ -75,3 +71,13 @@ public enum IMEApp { } } } + +public extension Date { + var stringTag: String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyyMMdd.HHmm" + dateFormatter.timeZone = .init(secondsFromGMT: +28800) ?? .current + let strDate = dateFormatter.string(from: self) + return strDate + } +} diff --git a/Packages/vChewing_MainAssembly/Sources/MainAssembly/SessionCtl_HandleDisplay.swift b/Packages/vChewing_MainAssembly/Sources/MainAssembly/SessionCtl_HandleDisplay.swift index a3881bc1..d0bebb9c 100644 --- a/Packages/vChewing_MainAssembly/Sources/MainAssembly/SessionCtl_HandleDisplay.swift +++ b/Packages/vChewing_MainAssembly/Sources/MainAssembly/SessionCtl_HandleDisplay.swift @@ -146,12 +146,12 @@ public extension SessionCtl { /// Instructions for Apple Developer relations to reveal this bug: /// /// 0) Please go to Step 1. Reason: IMK Candidate Window support has been removed in this repo. - /// 1) Make sure the usage of ".languageIdentifier" is disabled in the Dev Zone of the vChewing SSPreferences. + /// 1) Make sure the usage of ".languageIdentifier" is disabled in the Dev Zone of the vChewing Preferences. /// 2) Run "make update" in the project folder to download the latest git-submodule of dictionary file. /// 3) Compile the target "vChewingInstaller", run it. It will install the input method into /// "~/Library/Input Methods/" folder. Remember to ENABLE BOTH "vChewing-CHS" /// and "vChewing-CHT" input sources in System Preferences / Settings. - /// 4) Type Zhuyin "ej3" (ㄍㄨˇ) (or "gu3" in Pinyin if you enabled Pinyin typing in vChewing SSPreferences.) + /// 4) Type Zhuyin "ej3" (ㄍㄨˇ) (or "gu3" in Pinyin if you enabled Pinyin typing in vChewing Preferences.) /// using both "vChewing-CHS" and "vChewing-CHT", and check the candidate window by pressing SPACE key. /// 5) Do NOT enable either KangXi conversion mode nor JIS conversion mode. They are disabled by default. /// 6) Expecting the glyph differences of the candidate "骨" between PingFang SC and PingFang TC when rendering diff --git a/Source/Modules/SessionCtl_Menu.swift b/Source/Modules/SessionCtl_Menu.swift index 73289edf..8ddb18a5 100644 --- a/Source/Modules/SessionCtl_Menu.swift +++ b/Source/Modules/SessionCtl_Menu.swift @@ -9,7 +9,6 @@ import AppKit import MainAssembly import NotifierUI -import SSPreferences private extension Bool { var state: NSControl.StateValue { @@ -220,13 +219,9 @@ extension SessionCtl { public extension SessionCtl { @objc override func showPreferences(_: Any? = nil) { - osCheck: if #available(macOS 10.15, *) { + osCheck: if #available(macOS 13, *) { switch NSEvent.keyModifierFlags { case .option: break osCheck - // case .shift: - // CtlPrefUIShared.shared.controller.show(preferencePane: PrefUITabs.tabGeneral.ssPaneIdentifier) - // CtlPrefUIShared.shared.controller.window?.level = .statusBar - // CtlPrefUIShared.shared.controller.window?.setPosition(vertical: .top, horizontal: .right, padding: 20) default: CtlPrefUI.show() } NSApp.popup() diff --git a/Source/Modules/UIModules/PrefUI/CtlPrefUI.swift b/Source/Modules/UIModules/PrefUI/CtlPrefUI.swift index a6ff2163..2a3f497c 100644 --- a/Source/Modules/UIModules/PrefUI/CtlPrefUI.swift +++ b/Source/Modules/UIModules/PrefUI/CtlPrefUI.swift @@ -18,35 +18,6 @@ private let kWindowTitleHeight: Double = 78 @available(macOS 10.15, *) class CtlPrefUI: NSWindowController, NSWindowDelegate { - static var vwrGeneral: NSView = generateView(tab: .tabGeneral) - static var vwrCandidates: NSView = generateView(tab: .tabCandidates) - static var vwrBehavior: NSView = generateView(tab: .tabBehavior) - static var vwrOutput: NSView = generateView(tab: .tabOutput) - static var vwrDictionary: NSView = generateView(tab: .tabDictionary) - static var vwrPhrases: NSView = generateView(tab: .tabPhrases) - static var vwrCassette: NSView = generateView(tab: .tabCassette) - static var vwrKeyboard: NSView = generateView(tab: .tabKeyboard) - static var vwrDevZone: NSView = generateView(tab: .tabDevZone) - - static func generateView(tab: PrefUITabs) -> NSView { - var body: some View { - Group { - switch tab { - case .tabGeneral: VwrPrefPaneGeneral() - case .tabCandidates: VwrPrefPaneCandidates() - case .tabBehavior: VwrPrefPaneBehavior() - case .tabOutput: VwrPrefPaneOutput() - case .tabDictionary: VwrPrefPaneDictionary() - case .tabPhrases: VwrPrefPanePhrases() - case .tabCassette: VwrPrefPaneCassette() - case .tabKeyboard: VwrPrefPaneKeyboard() - case .tabDevZone: VwrPrefPaneDevZone() - } - }.fixedSize() - } - return NSHostingView(rootView: body.edgesIgnoringSafeArea(.top)) - } - public static var shared: CtlPrefUI? static func show() { @@ -76,48 +47,36 @@ class CtlPrefUI: NSWindowController, NSWindowDelegate { override func windowDidLoad() { super.windowDidLoad() window?.setPosition(vertical: .top, horizontal: .right, padding: 20) - - var preferencesTitleName = NSLocalizedString("vChewing Preferences…", comment: "") - preferencesTitleName.removeLast() - + if #available(macOS 13, *) { + window?.contentView = NSHostingView( + rootView: VwrSettingsUI() + .fixedSize(horizontal: true, vertical: false) + .ignoresSafeArea() + ) + } let toolbar = NSToolbar(identifier: "preference toolbar") toolbar.allowsUserCustomization = false toolbar.autosavesConfiguration = false toolbar.sizeMode = .default toolbar.delegate = self - toolbar.selectedItemIdentifier = PrefUITabs.tabGeneral.toolbarIdentifier + toolbar.selectedItemIdentifier = nil toolbar.showsBaselineSeparator = true - if #available(macOS 11.0, *) { - window?.toolbarStyle = .preference + if #available(macOS 11, *) { + window?.toolbarStyle = .unifiedCompact } window?.toolbar = toolbar - window?.title = "\(preferencesTitleName) (\(IMEApp.appVersionLabel))" - window?.titlebarAppearsTransparent = false - use(view: Self.vwrGeneral, animate: false) + var preferencesTitleName = NSLocalizedString("vChewing Preferences…", comment: "") + preferencesTitleName.removeLast() + window?.title = preferencesTitleName } } -// MARK: - NSToolbarDelegate Methods +// MARK: - NSToolbarDelegate. @available(macOS 10.15, *) extension CtlPrefUI: NSToolbarDelegate { - func use(view newView: NSView, animate: Bool = true) { - // 強制重置語彙編輯器畫面。 - if window?.contentView == Self.vwrPhrases || newView == Self.vwrPhrases { - Self.vwrPhrases = Self.generateView(tab: .tabPhrases) - } - guard let window = window, let existingContentView = window.contentView else { return } - let temporaryViewOld = NSView(frame: existingContentView.frame) - window.contentView = temporaryViewOld - var newWindowRect = NSRect(origin: window.frame.origin, size: newView.fittingSize) - newWindowRect.size.height += kWindowTitleHeight - newWindowRect.origin.y = window.frame.maxY - newWindowRect.height - window.setFrame(newWindowRect, display: true, animate: animate) - window.contentView = newView - } - var toolbarIdentifiers: [NSToolbarItem.Identifier] { - PrefUITabs.allCases.map(\.toolbarIdentifier) + [.init("Collapse or Expand Sidebar")] } func toolbarDefaultItemIdentifiers(_: NSToolbar) -> [NSToolbarItem.Identifier] { @@ -129,36 +88,21 @@ extension CtlPrefUI: NSToolbarDelegate { } func toolbarSelectableItemIdentifiers(_: NSToolbar) -> [NSToolbarItem.Identifier] { - toolbarIdentifiers - } - - @objc func updateTab(_ target: NSToolbarItem) { - guard let tab = PrefUITabs.fromInt(target.tag) else { return } - switch tab { - case .tabGeneral: use(view: Self.vwrGeneral) - case .tabCandidates: use(view: Self.vwrCandidates) - case .tabBehavior: use(view: Self.vwrBehavior) - case .tabOutput: use(view: Self.vwrOutput) - case .tabDictionary: use(view: Self.vwrDictionary) - case .tabPhrases: use(view: Self.vwrPhrases) - case .tabCassette: use(view: Self.vwrCassette) - case .tabKeyboard: use(view: Self.vwrKeyboard) - case .tabDevZone: use(view: Self.vwrDevZone) - } - window?.toolbar?.selectedItemIdentifier = tab.toolbarIdentifier + [] } func toolbar( _: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar _: Bool ) -> NSToolbarItem? { - guard let tab = PrefUITabs(rawValue: itemIdentifier.rawValue) else { return nil } let item = NSToolbarItem(itemIdentifier: itemIdentifier) - item.target = self - item.image = tab.icon - item.label = tab.i18nTitle - item.tag = tab.cocoaTag - item.action = #selector(updateTab(_:)) + if #available(macOS 11.0, *) { + item.isNavigational = true + } + item.target = window?.firstResponder + item.image = NSImage(named: "NSTouchBarSidebarTemplate") ?? .init() + item.tag = 0 + item.action = #selector(NSSplitViewController.toggleSidebar(_:)) return item } } diff --git a/Source/Modules/UIModules/PrefUI/CtlPrefUIShared.swift b/Source/Modules/UIModules/PrefUI/CtlPrefUIShared.swift index c3f2ad07..7168af10 100644 --- a/Source/Modules/UIModules/PrefUI/CtlPrefUIShared.swift +++ b/Source/Modules/UIModules/PrefUI/CtlPrefUIShared.swift @@ -7,55 +7,31 @@ // requirements defined in MIT License. import MainAssembly -import SSPreferences import SwiftExtension import SwiftUI @available(macOS 10.15, *) extension PrefUITabs { - var ssPaneIdentifier: SSPreferences.Settings.PaneIdentifier { .init(rawValue: rawValue) } -} - -@available(macOS 10.15, *) -struct VwrPrefPage: View { - @State var tabType: PrefUITabs - var body: some View { - Group { - switch tabType { - case .tabGeneral: VwrPrefPaneGeneral() - case .tabCandidates: VwrPrefPaneCandidates() - case .tabBehavior: VwrPrefPaneBehavior() - case .tabOutput: VwrPrefPaneOutput() - case .tabDictionary: VwrPrefPaneDictionary() - case .tabPhrases: VwrPrefPanePhrases() - case .tabCassette: VwrPrefPaneCassette() - case .tabKeyboard: VwrPrefPaneKeyboard() - case .tabDevZone: VwrPrefPaneDevZone() - } - }.fixedSize() + @ViewBuilder + var suiView: some View { + switch self { + case .tabGeneral: VwrPrefPaneGeneral() + case .tabCandidates: VwrPrefPaneCandidates() + case .tabBehavior: VwrPrefPaneBehavior() + case .tabOutput: VwrPrefPaneOutput() + case .tabDictionary: VwrPrefPaneDictionary() + case .tabPhrases: VwrPrefPanePhrases() + case .tabCassette: VwrPrefPaneCassette() + case .tabKeyboard: VwrPrefPaneKeyboard() + case .tabDevZone: VwrPrefPaneDevZone() + } } } @available(macOS 10.15, *) class CtlPrefUIShared { - var controller = PreferencesWindowController( - panes: { - var result = [PreferencePaneConvertible]() - PrefUITabs.allCases.forEach { neta in - let item: PreferencePaneConvertible = SSPreferences.Settings.Pane( - identifier: SSPreferences.Settings.PaneIdentifier(rawValue: neta.rawValue), - title: neta.i18nTitle, toolbarIcon: neta.icon, - contentView: { VwrPrefPage(tabType: neta) } - ) - result.append(item) - } - return result - }(), - style: .toolbarItems - ) - static var sharedWindow: NSWindow? { - CtlPrefUI.shared?.window ?? CtlPrefUIShared.shared.controller.window + CtlPrefUI.shared?.window } static let shared = CtlPrefUIShared() @@ -73,18 +49,6 @@ class CtlPrefUIShared { }() static let contentMaxHeight: Double = 490 - static let contentWidth: Double = { - switch PrefMgr.shared.appleLanguages[0] { - case "ja": - return 520 - default: - if PrefMgr.shared.appleLanguages[0].contains("zh-Han") { - return 500 - } else { - return 580 - } - } - }() static let formWidth: Double = { switch PrefMgr.shared.appleLanguages[0] { @@ -102,9 +66,6 @@ class CtlPrefUIShared { static var isCJKInterface: Bool { PrefMgr.shared.appleLanguages[0].contains("zh-Han") || PrefMgr.shared.appleLanguages[0] == "ja" } - - static var containerWidth: Double { contentWidth + 60 } - static var maxDescriptionWidth: Double { contentWidth * 0.8 } } @available(macOS 10.15, *) @@ -121,6 +82,6 @@ public extension View { public extension View { func formStyled() -> some View { if #available(macOS 13, *) { return self.formStyle(.grouped) } - return self.padding() + return padding() } } diff --git a/Source/Modules/UIModules/PrefUI/VwrPrefPaneBehavior.swift b/Source/Modules/UIModules/PrefUI/VwrPrefPaneBehavior.swift index 643ad0e5..dab08aaf 100644 --- a/Source/Modules/UIModules/PrefUI/VwrPrefPaneBehavior.swift +++ b/Source/Modules/UIModules/PrefUI/VwrPrefPaneBehavior.swift @@ -8,7 +8,6 @@ import MainAssembly import Shared -import SSPreferences import SwiftExtension import SwiftUI import SwiftUIBackports @@ -252,7 +251,7 @@ struct VwrPrefPaneBehavior: View { .settingsDescription() } } - }.formStyled().frame(width: CtlPrefUIShared.formWidth) + }.formStyled().frame(minWidth: CtlPrefUIShared.formWidth, maxWidth: ceil(CtlPrefUIShared.formWidth * 1.2)) } .frame(maxHeight: CtlPrefUIShared.contentMaxHeight) } diff --git a/Source/Modules/UIModules/PrefUI/VwrPrefPaneCandidates.swift b/Source/Modules/UIModules/PrefUI/VwrPrefPaneCandidates.swift index 520a027a..cee1cf20 100644 --- a/Source/Modules/UIModules/PrefUI/VwrPrefPaneCandidates.swift +++ b/Source/Modules/UIModules/PrefUI/VwrPrefPaneCandidates.swift @@ -8,7 +8,6 @@ import MainAssembly import Shared -import SSPreferences import SwiftExtension import SwiftUI import SwiftUIBackports @@ -178,7 +177,7 @@ struct VwrPrefPaneCandidates: View { // MARK: (header: Text("Experimental:")) let imkEOSNoticeButton = Button("Where's IMK Candidate Window?") { - if let window = CtlPrefUIShared.sharedWindow { + if let window = CtlPrefUI.shared?.window { let title = "The End of Support for IMK Candidate Window" let explanation = "1) Only macOS has IMKCandidates. Since it relies on a dedicated ObjC Bridging Header to expose necessary internal APIs to work, it hinders vChewing from completely modularized for multi-platform support.\n\n2) IMKCandidates is buggy. It is not likely to be completely fixed by Apple, and its devs are not allowed to talk about it to non-Apple individuals. That's why we have had enough with IMKCandidates. It is likely the reason why Apple had never used IMKCandidates in their official InputMethodKit sample projects (as of August 2023)." window.callAlert(title: title.localized, text: explanation.localized) @@ -191,7 +190,7 @@ struct VwrPrefPaneCandidates: View { isOn: $enableMouseScrollingForTDKCandidatesCocoa ) } - }.formStyled().frame(width: CtlPrefUIShared.formWidth) + }.formStyled().frame(minWidth: CtlPrefUIShared.formWidth, maxWidth: ceil(CtlPrefUIShared.formWidth * 1.2)) } .frame(maxHeight: CtlPrefUIShared.contentMaxHeight) } @@ -226,7 +225,7 @@ private struct VwrPrefPaneCandidates_SelectionKeys: View { let keys = value.trimmingCharacters(in: .whitespacesAndNewlines).lowercased().deduplicated // Start Error Handling. if let errorResult = CandidateKey.validate(keys: keys) { - if let window = CtlPrefUIShared.sharedWindow, !keys.isEmpty { + if let window = CtlPrefUI.shared?.window, !keys.isEmpty { IMEApp.buzz() let alert = NSAlert(error: NSLocalizedString("Invalid Selection Keys.", comment: "")) alert.informativeText = errorResult diff --git a/Source/Modules/UIModules/PrefUI/VwrPrefPaneCassette.swift b/Source/Modules/UIModules/PrefUI/VwrPrefPaneCassette.swift index fe065f83..85cbe824 100644 --- a/Source/Modules/UIModules/PrefUI/VwrPrefPaneCassette.swift +++ b/Source/Modules/UIModules/PrefUI/VwrPrefPaneCassette.swift @@ -9,7 +9,6 @@ import BookmarkManager import MainAssembly import Shared -import SSPreferences import SwiftExtension import SwiftUI import SwiftUIBackports @@ -86,7 +85,7 @@ struct VwrPrefPaneCassette: View { let bolPreviousPathValidity = LMMgr.checkCassettePathValidity( cassettePath.expandingTildeInPath) - if let window = CtlPrefUIShared.sharedWindow { + if let window = CtlPrefUI.shared?.window { Self.dlgOpenFile.beginSheetModal(for: window) { result in if result == NSApplication.ModalResponse.OK { guard let url = Self.dlgOpenFile.url else { return } @@ -129,7 +128,7 @@ struct VwrPrefPaneCassette: View { LocalizedStringKey("Enable cassette mode, suppressing phonabet input"), isOn: $cassetteEnabled.onChange { if cassetteEnabled, !LMMgr.checkCassettePathValidity(cassettePath) { - if let window = CtlPrefUIShared.sharedWindow { + if let window = CtlPrefUI.shared?.window { IMEApp.buzz() let alert = NSAlert(error: NSLocalizedString("Path invalid or file access error.", comment: "")) alert.informativeText = NSLocalizedString( @@ -186,7 +185,7 @@ struct VwrPrefPaneCassette: View { .settingsDescription() } } - }.formStyled().frame(width: CtlPrefUIShared.formWidth) + }.formStyled().frame(minWidth: CtlPrefUIShared.formWidth, maxWidth: ceil(CtlPrefUIShared.formWidth * 1.2)) } .frame(maxHeight: CtlPrefUIShared.contentMaxHeight) } diff --git a/Source/Modules/UIModules/PrefUI/VwrPrefPaneDevZone.swift b/Source/Modules/UIModules/PrefUI/VwrPrefPaneDevZone.swift index 95225472..577af25c 100644 --- a/Source/Modules/UIModules/PrefUI/VwrPrefPaneDevZone.swift +++ b/Source/Modules/UIModules/PrefUI/VwrPrefPaneDevZone.swift @@ -7,7 +7,6 @@ // requirements defined in MIT License. import Shared -import SSPreferences import SwiftExtension import SwiftUI import SwiftUIBackports @@ -57,7 +56,7 @@ struct VwrPrefPaneDevZone: View { .settingsDescription() } } - }.formStyled().frame(width: CtlPrefUIShared.formWidth) + }.formStyled().frame(minWidth: CtlPrefUIShared.formWidth, maxWidth: ceil(CtlPrefUIShared.formWidth * 1.2)) } .frame(maxHeight: CtlPrefUIShared.contentMaxHeight) } diff --git a/Source/Modules/UIModules/PrefUI/VwrPrefPaneDictionary.swift b/Source/Modules/UIModules/PrefUI/VwrPrefPaneDictionary.swift index 716e2fdd..deba01d9 100644 --- a/Source/Modules/UIModules/PrefUI/VwrPrefPaneDictionary.swift +++ b/Source/Modules/UIModules/PrefUI/VwrPrefPaneDictionary.swift @@ -10,7 +10,6 @@ import BookmarkManager import CocoaExtension import MainAssembly import Shared -import SSPreferences import SwiftExtension import SwiftUI import SwiftUIBackports @@ -107,7 +106,7 @@ struct VwrPrefPaneDictionary: View { let bolPreviousFolderValidity = LMMgr.checkIfSpecifiedUserDataFolderValid( userDataFolderSpecified.expandingTildeInPath) - if let window = CtlPrefUIShared.sharedWindow { + if let window = CtlPrefUI.shared?.window { Self.dlgOpenPath.beginSheetModal(for: window) { result in if result == NSApplication.ModalResponse.OK { guard let url = Self.dlgOpenPath.url else { return } @@ -233,7 +232,7 @@ struct VwrPrefPaneDictionary: View { .settingsDescription() } } - }.formStyled().frame(width: CtlPrefUIShared.formWidth) + }.formStyled().frame(minWidth: CtlPrefUIShared.formWidth, maxWidth: ceil(CtlPrefUIShared.formWidth * 1.2)) } .frame(maxHeight: CtlPrefUIShared.contentMaxHeight) } diff --git a/Source/Modules/UIModules/PrefUI/VwrPrefPaneGeneral.swift b/Source/Modules/UIModules/PrefUI/VwrPrefPaneGeneral.swift index 80eba947..e5c7e204 100644 --- a/Source/Modules/UIModules/PrefUI/VwrPrefPaneGeneral.swift +++ b/Source/Modules/UIModules/PrefUI/VwrPrefPaneGeneral.swift @@ -8,7 +8,6 @@ import MainAssembly import Shared -import SSPreferences import SwiftExtension import SwiftUI import SwiftUIBackports @@ -144,7 +143,7 @@ struct VwrPrefPaneGeneral: View { } } alert.addButton(withTitle: NSLocalizedString("Leave it checked", comment: "")) - if let window = CtlPrefUIShared.sharedWindow, !shouldNotFartInLieuOfBeep { + if let window = CtlPrefUI.shared?.window, !shouldNotFartInLieuOfBeep { shouldNotFartInLieuOfBeep = true alert.beginSheetModal(for: window) { result in switch result { @@ -176,7 +175,7 @@ struct VwrPrefPaneGeneral: View { isOn: $isDebugModeEnabled ) } - }.formStyled().frame(width: CtlPrefUIShared.formWidth) + }.formStyled().frame(minWidth: CtlPrefUIShared.formWidth, maxWidth: ceil(CtlPrefUIShared.formWidth * 1.2)) } .frame(maxHeight: CtlPrefUIShared.contentMaxHeight) } diff --git a/Source/Modules/UIModules/PrefUI/VwrPrefPaneKeyboard.swift b/Source/Modules/UIModules/PrefUI/VwrPrefPaneKeyboard.swift index 94503e97..52f10d71 100644 --- a/Source/Modules/UIModules/PrefUI/VwrPrefPaneKeyboard.swift +++ b/Source/Modules/UIModules/PrefUI/VwrPrefPaneKeyboard.swift @@ -9,7 +9,6 @@ import IMKUtils import MainAssembly import Shared -import SSPreferences import SwiftExtension import SwiftUI import SwiftUIBackports @@ -126,7 +125,7 @@ struct VwrPrefPaneKeyboard: View { Section(header: Text("Keyboard Shortcuts:")) { VwrPrefPaneKeyboard_KeyboardShortcuts() } - }.formStyled().frame(width: CtlPrefUIShared.formWidth) + }.formStyled().frame(minWidth: CtlPrefUIShared.formWidth, maxWidth: ceil(CtlPrefUIShared.formWidth * 1.2)) } .frame(maxHeight: CtlPrefUIShared.contentMaxHeight) } diff --git a/Source/Modules/UIModules/PrefUI/VwrPrefPaneOutput.swift b/Source/Modules/UIModules/PrefUI/VwrPrefPaneOutput.swift index 49266066..0a057113 100644 --- a/Source/Modules/UIModules/PrefUI/VwrPrefPaneOutput.swift +++ b/Source/Modules/UIModules/PrefUI/VwrPrefPaneOutput.swift @@ -7,7 +7,6 @@ // requirements defined in MIT License. import Shared -import SSPreferences import SwiftExtension import SwiftUI import SwiftUIBackports @@ -74,7 +73,7 @@ struct VwrPrefPaneOutput: View { .settingsDescription() } } - }.formStyled().frame(width: CtlPrefUIShared.formWidth) + }.formStyled().frame(minWidth: CtlPrefUIShared.formWidth, maxWidth: ceil(CtlPrefUIShared.formWidth * 1.2)) } .frame(maxHeight: CtlPrefUIShared.contentMaxHeight) } diff --git a/Source/Modules/UIModules/PrefUI/VwrPrefPanePhrases.swift b/Source/Modules/UIModules/PrefUI/VwrPrefPanePhrases.swift index 83030096..c4fceeea 100644 --- a/Source/Modules/UIModules/PrefUI/VwrPrefPanePhrases.swift +++ b/Source/Modules/UIModules/PrefUI/VwrPrefPanePhrases.swift @@ -9,7 +9,6 @@ import MainAssembly import PhraseEditorUI import Shared -import SSPreferences import SwiftExtension import SwiftUI @@ -19,7 +18,7 @@ struct VwrPrefPanePhrases: View { ScrollView { VStack { GroupBox { - VwrPhraseEditorUI(delegate: LMMgr.shared, window: CtlPrefUIShared.sharedWindow) + VwrPhraseEditorUI(delegate: LMMgr.shared, window: CtlPrefUI.shared?.window) .padding(4) .frame(maxWidth: .infinity) .frame(height: 440) @@ -27,7 +26,7 @@ struct VwrPrefPanePhrases: View { } .padding(4) .padding() - .frame(width: CtlPrefUIShared.formWidth) + .frame(minWidth: CtlPrefUIShared.formWidth, maxWidth: ceil(CtlPrefUIShared.formWidth * 1.2)) } .frame(maxHeight: CtlPrefUIShared.contentMaxHeight) } diff --git a/Source/Modules/UIModules/PrefUI/VwrSettingsUI.swift b/Source/Modules/UIModules/PrefUI/VwrSettingsUI.swift new file mode 100644 index 00000000..d8999fef --- /dev/null +++ b/Source/Modules/UIModules/PrefUI/VwrSettingsUI.swift @@ -0,0 +1,52 @@ +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// ==================== +// This code is released under the MIT license (SPDX-License-Identifier: MIT) +// ... with NTL restriction stating that: +// No trademark license is granted to use the trade names, trademarks, service +// marks, or product names of Contributor, except as required to fulfill notice +// requirements defined in MIT License. + +import MainAssembly +import SwiftUI + +@available(macOS 13, *) +public struct VwrSettingsUI: View { + @State private var selectedTabID: PrefUITabs? + public init() {} + + /// 雖說從 macOS 13 開始的正確做法是使用 NavigationSplitView, + /// 但由於這個畫面是藉由 NSHostingView 叫出來的、所以無法正確處理大型標題列。 + /// 目前還是暫時繼續用 NavigationView。 + public var body: some View { + NavigationView { + VStack { + List(PrefUITabs.allCases, selection: $selectedTabID) { neta in + if neta == PrefUITabs.tabGeneral { + if let appIcon = NSImage(named: "IconSansMargin") { + Image(nsImage: appIcon).resizable() + .frame(width: 86, height: 86) + .frame(maxWidth: .infinity, alignment: .leading) + .padding([.top, .bottom], NSFont.systemFontSize / 2) + } + } + NavigationLink(destination: neta.suiView) { + Label { + Text(verbatim: neta.i18nTitle) + } icon: { + Image(nsImage: neta.icon) + } + } + } + Spacer() + VStack(alignment: .leading) { + Text("v" + IMEApp.appMainVersionLabel.joined(separator: " Build ")) + Text(IMEApp.appSignedDateLabel) + }.settingsDescription().padding() + } + .navigationTitle(PrefUITabs.tabGeneral.i18nTitle) + .frame(minWidth: 128, idealWidth: 128, maxWidth: 128) + PrefUITabs.tabGeneral.suiView + } + .frame(width: CtlPrefUIShared.formWidth + 140, height: CtlPrefUIShared.contentMaxHeight) + } +} diff --git a/Source/Modules/UIModules/PrefUITabs.swift b/Source/Modules/UIModules/PrefUITabs.swift index 09f067e2..fb22bf91 100644 --- a/Source/Modules/UIModules/PrefUITabs.swift +++ b/Source/Modules/UIModules/PrefUITabs.swift @@ -10,7 +10,8 @@ import AppKit import MainAssembly import Shared -enum PrefUITabs: String, CaseIterable { +enum PrefUITabs: String, CaseIterable, Identifiable, Hashable { + var id: ObjectIdentifier { .init(rawValue as NSString) } case tabGeneral = "General" case tabCandidates = "Candidates" case tabBehavior = "Behavior" diff --git a/Source/Modules/WindowControllers/CtlPrefWindow.swift b/Source/Modules/WindowControllers/CtlPrefWindow.swift index 39400f83..ea785684 100644 --- a/Source/Modules/WindowControllers/CtlPrefWindow.swift +++ b/Source/Modules/WindowControllers/CtlPrefWindow.swift @@ -124,7 +124,7 @@ class CtlPrefWindow: NSWindowController, NSWindowDelegate { toolbar.delegate = self toolbar.selectedItemIdentifier = PrefUITabs.tabGeneral.toolbarIdentifier toolbar.showsBaselineSeparator = true - if #available(macOS 11.0, *) { + if #available(macOS 13, *) { window?.toolbarStyle = .preference } window?.toolbar = toolbar diff --git a/Source/Resources/Images/Images.xcassets/IconSansMargin.imageset/Contents.json b/Source/Resources/Images/Images.xcassets/IconSansMargin.imageset/Contents.json new file mode 100644 index 00000000..4c2dea95 --- /dev/null +++ b/Source/Resources/Images/Images.xcassets/IconSansMargin.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "IconSansMargin.heic", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Source/Resources/Images/Images.xcassets/IconSansMargin.imageset/IconSansMargin.heic b/Source/Resources/Images/Images.xcassets/IconSansMargin.imageset/IconSansMargin.heic new file mode 100644 index 00000000..1c3441da Binary files /dev/null and b/Source/Resources/Images/Images.xcassets/IconSansMargin.imageset/IconSansMargin.heic differ diff --git a/Source/Resources/Images/RAW/Icon-vChewingPhraseEditor.pxd b/Source/Resources/Images/RAW/Icon-vChewingPhraseEditor.pxd deleted file mode 100644 index 444755c3..00000000 Binary files a/Source/Resources/Images/RAW/Icon-vChewingPhraseEditor.pxd and /dev/null differ diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 94bc4794..1bdea09d 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ 5B30BF282944867800BD87A9 /* CtlRevLookupWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B30BF272944867800BD87A9 /* CtlRevLookupWindow.swift */; }; 5B40113928D7050D00A9D4CB /* Shared in Frameworks */ = {isa = PBXBuildFile; productRef = 5B40113828D7050D00A9D4CB /* Shared */; }; 5B40113C28D71C0100A9D4CB /* Uninstaller in Frameworks */ = {isa = PBXBuildFile; productRef = 5B40113B28D71C0100A9D4CB /* Uninstaller */; }; + 5B4C59AE2ABCB18700098626 /* VwrSettingsUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B4C59AD2ABCB18700098626 /* VwrSettingsUI.swift */; }; 5B5A603028E81CC50001AE8D /* SwiftUIBackports in Frameworks */ = {isa = PBXBuildFile; productRef = 5B5A602F28E81CC50001AE8D /* SwiftUIBackports */; }; 5B62A33D27AE7CC100A19448 /* CtlAboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33C27AE7CC100A19448 /* CtlAboutWindow.swift */; }; 5B65FB342A9518DA007EEFB0 /* MainAssembly in Frameworks */ = {isa = PBXBuildFile; productRef = 5B65FB332A9518DA007EEFB0 /* MainAssembly */; }; @@ -187,6 +188,7 @@ 5B30F11227BA568800484E24 /* vChewingKeyLayout.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = vChewingKeyLayout.bundle; sourceTree = ""; }; 5B312684287800DE001AA720 /* FAQ.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = FAQ.md; sourceTree = ""; }; 5B40113A28D71B8700A9D4CB /* vChewing_Uninstaller */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_Uninstaller; path = Packages/vChewing_Uninstaller; sourceTree = ""; }; + 5B4C59AD2ABCB18700098626 /* VwrSettingsUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VwrSettingsUI.swift; sourceTree = ""; }; 5B5A602E28E81CB00001AE8D /* ShapsBenkau_SwiftUIBackports */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = ShapsBenkau_SwiftUIBackports; path = Packages/ShapsBenkau_SwiftUIBackports; sourceTree = ""; }; 5B62A33C27AE7CC100A19448 /* CtlAboutWindow.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = CtlAboutWindow.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5B65B919284D0185007C558B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; @@ -475,6 +477,7 @@ 5B2E009328FD1E8100E78D6E /* VwrPrefPaneCassette.swift */, 5BA9FD0B27FEDB6B002DE248 /* VwrPrefPaneKeyboard.swift */, 5B78EE0C28A562B4009456C1 /* VwrPrefPaneDevZone.swift */, + 5B4C59AD2ABCB18700098626 /* VwrSettingsUI.swift */, ); path = PrefUI; sourceTree = ""; @@ -984,6 +987,7 @@ 5BA9FD1127FEDB6B002DE248 /* CtlPrefUIShared.swift in Sources */, 5BA9FD1327FEDB6B002DE248 /* VwrPrefPaneDictionary.swift in Sources */, 5BF7548929B2F04F00FA50DA /* CtlPrefUI.swift in Sources */, + 5B4C59AE2ABCB18700098626 /* VwrSettingsUI.swift in Sources */, 5BF018F9299923BD00248CDD /* VwrPrefPaneBehavior.swift in Sources */, 5BB802DA27FABA8300CF1C19 /* SessionCtl_Menu.swift in Sources */, 5B2E009428FD1E8100E78D6E /* VwrPrefPaneCassette.swift in Sources */,