PrefUI // Massive renovation - phase 2.

- This is the last workable commit prior to boosting the SwiftUI system requirements from macOS 10.15 Catalina to macOS 13 Ventura.
This commit is contained in:
ShikiSuen 2023-09-24 02:43:23 +08:00
parent 37104cb8a9
commit f81f2d3b43
21 changed files with 165 additions and 190 deletions

View File

@ -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
}
}

View File

@ -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

View File

@ -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()

View File

@ -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
}
}

View File

@ -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()
}
}

View File

@ -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)
}

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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"

View File

@ -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

View File

@ -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
}
}

View File

@ -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 = "<group>"; };
5B312684287800DE001AA720 /* FAQ.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = FAQ.md; sourceTree = "<group>"; };
5B40113A28D71B8700A9D4CB /* vChewing_Uninstaller */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_Uninstaller; path = Packages/vChewing_Uninstaller; sourceTree = "<group>"; };
5B4C59AD2ABCB18700098626 /* VwrSettingsUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VwrSettingsUI.swift; sourceTree = "<group>"; };
5B5A602E28E81CB00001AE8D /* ShapsBenkau_SwiftUIBackports */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = ShapsBenkau_SwiftUIBackports; path = Packages/ShapsBenkau_SwiftUIBackports; sourceTree = "<group>"; };
5B62A33C27AE7CC100A19448 /* CtlAboutWindow.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = CtlAboutWindow.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
5B65B919284D0185007C558B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
@ -475,6 +477,7 @@
5B2E009328FD1E8100E78D6E /* VwrPrefPaneCassette.swift */,
5BA9FD0B27FEDB6B002DE248 /* VwrPrefPaneKeyboard.swift */,
5B78EE0C28A562B4009456C1 /* VwrPrefPaneDevZone.swift */,
5B4C59AD2ABCB18700098626 /* VwrSettingsUI.swift */,
);
path = PrefUI;
sourceTree = "<group>";
@ -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 */,