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: - // MARK: -
public static let appVersionLabel: String = { 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? = { let maybeDateModified: Date? = {
guard let executableURL = Bundle.main.executableURL, guard let executableURL = Bundle.main.executableURL,
let infoDate = (try? executableURL.resourceValues(forKeys: [.contentModificationDateKey]))?.contentModificationDate let infoDate = (try? executableURL.resourceValues(forKeys: [.contentModificationDateKey]))?.contentModificationDate
@ -25,32 +40,13 @@ public enum IMEApp {
return infoDate 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() { if let theDate = Bundle.main.getCodeSignedDate() {
theResults.append(dateStringTag(date: theDate)) return theDate.stringTag
} else if let theDate = maybeDateModified { } else if let theDate = maybeDateModified {
theResults.append("\(dateStringTag(date: theDate)) Unsigned") return "\(theDate.stringTag) Unsigned"
} else { } else {
theResults.append("Unsigned") return "Unsigned"
} }
return theResults.joined(separator: " - ")
}() }()
// MARK: - // 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: /// 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. /// 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. /// 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 /// 3) Compile the target "vChewingInstaller", run it. It will install the input method into
/// "~/Library/Input Methods/" folder. Remember to ENABLE BOTH "vChewing-CHS" /// "~/Library/Input Methods/" folder. Remember to ENABLE BOTH "vChewing-CHS"
/// and "vChewing-CHT" input sources in System Preferences / Settings. /// 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. /// 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. /// 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 /// 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 AppKit
import MainAssembly import MainAssembly
import NotifierUI import NotifierUI
import SSPreferences
private extension Bool { private extension Bool {
var state: NSControl.StateValue { var state: NSControl.StateValue {
@ -220,13 +219,9 @@ extension SessionCtl {
public extension SessionCtl { public extension SessionCtl {
@objc override func showPreferences(_: Any? = nil) { @objc override func showPreferences(_: Any? = nil) {
osCheck: if #available(macOS 10.15, *) { osCheck: if #available(macOS 13, *) {
switch NSEvent.keyModifierFlags { switch NSEvent.keyModifierFlags {
case .option: break osCheck 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() default: CtlPrefUI.show()
} }
NSApp.popup() NSApp.popup()

View File

@ -18,35 +18,6 @@ private let kWindowTitleHeight: Double = 78
@available(macOS 10.15, *) @available(macOS 10.15, *)
class CtlPrefUI: NSWindowController, NSWindowDelegate { 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? public static var shared: CtlPrefUI?
static func show() { static func show() {
@ -76,48 +47,36 @@ class CtlPrefUI: NSWindowController, NSWindowDelegate {
override func windowDidLoad() { override func windowDidLoad() {
super.windowDidLoad() super.windowDidLoad()
window?.setPosition(vertical: .top, horizontal: .right, padding: 20) window?.setPosition(vertical: .top, horizontal: .right, padding: 20)
if #available(macOS 13, *) {
var preferencesTitleName = NSLocalizedString("vChewing Preferences…", comment: "") window?.contentView = NSHostingView(
preferencesTitleName.removeLast() rootView: VwrSettingsUI()
.fixedSize(horizontal: true, vertical: false)
.ignoresSafeArea()
)
}
let toolbar = NSToolbar(identifier: "preference toolbar") let toolbar = NSToolbar(identifier: "preference toolbar")
toolbar.allowsUserCustomization = false toolbar.allowsUserCustomization = false
toolbar.autosavesConfiguration = false toolbar.autosavesConfiguration = false
toolbar.sizeMode = .default toolbar.sizeMode = .default
toolbar.delegate = self toolbar.delegate = self
toolbar.selectedItemIdentifier = PrefUITabs.tabGeneral.toolbarIdentifier toolbar.selectedItemIdentifier = nil
toolbar.showsBaselineSeparator = true toolbar.showsBaselineSeparator = true
if #available(macOS 11.0, *) { if #available(macOS 11, *) {
window?.toolbarStyle = .preference window?.toolbarStyle = .unifiedCompact
} }
window?.toolbar = toolbar window?.toolbar = toolbar
window?.title = "\(preferencesTitleName) (\(IMEApp.appVersionLabel))" var preferencesTitleName = NSLocalizedString("vChewing Preferences…", comment: "")
window?.titlebarAppearsTransparent = false preferencesTitleName.removeLast()
use(view: Self.vwrGeneral, animate: false) window?.title = preferencesTitleName
} }
} }
// MARK: - NSToolbarDelegate Methods // MARK: - NSToolbarDelegate.
@available(macOS 10.15, *) @available(macOS 10.15, *)
extension CtlPrefUI: NSToolbarDelegate { 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] { var toolbarIdentifiers: [NSToolbarItem.Identifier] {
PrefUITabs.allCases.map(\.toolbarIdentifier) [.init("Collapse or Expand Sidebar")]
} }
func toolbarDefaultItemIdentifiers(_: NSToolbar) -> [NSToolbarItem.Identifier] { func toolbarDefaultItemIdentifiers(_: NSToolbar) -> [NSToolbarItem.Identifier] {
@ -129,36 +88,21 @@ extension CtlPrefUI: NSToolbarDelegate {
} }
func toolbarSelectableItemIdentifiers(_: NSToolbar) -> [NSToolbarItem.Identifier] { 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( func toolbar(
_: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, _: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier,
willBeInsertedIntoToolbar _: Bool willBeInsertedIntoToolbar _: Bool
) -> NSToolbarItem? { ) -> NSToolbarItem? {
guard let tab = PrefUITabs(rawValue: itemIdentifier.rawValue) else { return nil }
let item = NSToolbarItem(itemIdentifier: itemIdentifier) let item = NSToolbarItem(itemIdentifier: itemIdentifier)
item.target = self if #available(macOS 11.0, *) {
item.image = tab.icon item.isNavigational = true
item.label = tab.i18nTitle }
item.tag = tab.cocoaTag item.target = window?.firstResponder
item.action = #selector(updateTab(_:)) item.image = NSImage(named: "NSTouchBarSidebarTemplate") ?? .init()
item.tag = 0
item.action = #selector(NSSplitViewController.toggleSidebar(_:))
return item return item
} }
} }

View File

@ -7,55 +7,31 @@
// requirements defined in MIT License. // requirements defined in MIT License.
import MainAssembly import MainAssembly
import SSPreferences
import SwiftExtension import SwiftExtension
import SwiftUI import SwiftUI
@available(macOS 10.15, *) @available(macOS 10.15, *)
extension PrefUITabs { extension PrefUITabs {
var ssPaneIdentifier: SSPreferences.Settings.PaneIdentifier { .init(rawValue: rawValue) } @ViewBuilder
} var suiView: some View {
switch self {
@available(macOS 10.15, *) case .tabGeneral: VwrPrefPaneGeneral()
struct VwrPrefPage: View { case .tabCandidates: VwrPrefPaneCandidates()
@State var tabType: PrefUITabs case .tabBehavior: VwrPrefPaneBehavior()
var body: some View { case .tabOutput: VwrPrefPaneOutput()
Group { case .tabDictionary: VwrPrefPaneDictionary()
switch tabType { case .tabPhrases: VwrPrefPanePhrases()
case .tabGeneral: VwrPrefPaneGeneral() case .tabCassette: VwrPrefPaneCassette()
case .tabCandidates: VwrPrefPaneCandidates() case .tabKeyboard: VwrPrefPaneKeyboard()
case .tabBehavior: VwrPrefPaneBehavior() case .tabDevZone: VwrPrefPaneDevZone()
case .tabOutput: VwrPrefPaneOutput() }
case .tabDictionary: VwrPrefPaneDictionary()
case .tabPhrases: VwrPrefPanePhrases()
case .tabCassette: VwrPrefPaneCassette()
case .tabKeyboard: VwrPrefPaneKeyboard()
case .tabDevZone: VwrPrefPaneDevZone()
}
}.fixedSize()
} }
} }
@available(macOS 10.15, *) @available(macOS 10.15, *)
class CtlPrefUIShared { 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? { static var sharedWindow: NSWindow? {
CtlPrefUI.shared?.window ?? CtlPrefUIShared.shared.controller.window CtlPrefUI.shared?.window
} }
static let shared = CtlPrefUIShared() static let shared = CtlPrefUIShared()
@ -73,18 +49,6 @@ class CtlPrefUIShared {
}() }()
static let contentMaxHeight: Double = 490 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 = { static let formWidth: Double = {
switch PrefMgr.shared.appleLanguages[0] { switch PrefMgr.shared.appleLanguages[0] {
@ -102,9 +66,6 @@ class CtlPrefUIShared {
static var isCJKInterface: Bool { static var isCJKInterface: Bool {
PrefMgr.shared.appleLanguages[0].contains("zh-Han") || PrefMgr.shared.appleLanguages[0] == "ja" 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, *) @available(macOS 10.15, *)
@ -121,6 +82,6 @@ public extension View {
public extension View { public extension View {
func formStyled() -> some View { func formStyled() -> some View {
if #available(macOS 13, *) { return self.formStyle(.grouped) } if #available(macOS 13, *) { return self.formStyle(.grouped) }
return self.padding() return padding()
} }
} }

View File

@ -8,7 +8,6 @@
import MainAssembly import MainAssembly
import Shared import Shared
import SSPreferences
import SwiftExtension import SwiftExtension
import SwiftUI import SwiftUI
import SwiftUIBackports import SwiftUIBackports
@ -252,7 +251,7 @@ struct VwrPrefPaneBehavior: View {
.settingsDescription() .settingsDescription()
} }
} }
}.formStyled().frame(width: CtlPrefUIShared.formWidth) }.formStyled().frame(minWidth: CtlPrefUIShared.formWidth, maxWidth: ceil(CtlPrefUIShared.formWidth * 1.2))
} }
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight) .frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
} }

View File

@ -8,7 +8,6 @@
import MainAssembly import MainAssembly
import Shared import Shared
import SSPreferences
import SwiftExtension import SwiftExtension
import SwiftUI import SwiftUI
import SwiftUIBackports import SwiftUIBackports
@ -178,7 +177,7 @@ struct VwrPrefPaneCandidates: View {
// MARK: (header: Text("Experimental:")) // MARK: (header: Text("Experimental:"))
let imkEOSNoticeButton = Button("Where's IMK Candidate Window?") { 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 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)." 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) window.callAlert(title: title.localized, text: explanation.localized)
@ -191,7 +190,7 @@ struct VwrPrefPaneCandidates: View {
isOn: $enableMouseScrollingForTDKCandidatesCocoa isOn: $enableMouseScrollingForTDKCandidatesCocoa
) )
} }
}.formStyled().frame(width: CtlPrefUIShared.formWidth) }.formStyled().frame(minWidth: CtlPrefUIShared.formWidth, maxWidth: ceil(CtlPrefUIShared.formWidth * 1.2))
} }
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight) .frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
} }
@ -226,7 +225,7 @@ private struct VwrPrefPaneCandidates_SelectionKeys: View {
let keys = value.trimmingCharacters(in: .whitespacesAndNewlines).lowercased().deduplicated let keys = value.trimmingCharacters(in: .whitespacesAndNewlines).lowercased().deduplicated
// Start Error Handling. // Start Error Handling.
if let errorResult = CandidateKey.validate(keys: keys) { if let errorResult = CandidateKey.validate(keys: keys) {
if let window = CtlPrefUIShared.sharedWindow, !keys.isEmpty { if let window = CtlPrefUI.shared?.window, !keys.isEmpty {
IMEApp.buzz() IMEApp.buzz()
let alert = NSAlert(error: NSLocalizedString("Invalid Selection Keys.", comment: "")) let alert = NSAlert(error: NSLocalizedString("Invalid Selection Keys.", comment: ""))
alert.informativeText = errorResult alert.informativeText = errorResult

View File

@ -9,7 +9,6 @@
import BookmarkManager import BookmarkManager
import MainAssembly import MainAssembly
import Shared import Shared
import SSPreferences
import SwiftExtension import SwiftExtension
import SwiftUI import SwiftUI
import SwiftUIBackports import SwiftUIBackports
@ -86,7 +85,7 @@ struct VwrPrefPaneCassette: View {
let bolPreviousPathValidity = LMMgr.checkCassettePathValidity( let bolPreviousPathValidity = LMMgr.checkCassettePathValidity(
cassettePath.expandingTildeInPath) cassettePath.expandingTildeInPath)
if let window = CtlPrefUIShared.sharedWindow { if let window = CtlPrefUI.shared?.window {
Self.dlgOpenFile.beginSheetModal(for: window) { result in Self.dlgOpenFile.beginSheetModal(for: window) { result in
if result == NSApplication.ModalResponse.OK { if result == NSApplication.ModalResponse.OK {
guard let url = Self.dlgOpenFile.url else { return } guard let url = Self.dlgOpenFile.url else { return }
@ -129,7 +128,7 @@ struct VwrPrefPaneCassette: View {
LocalizedStringKey("Enable cassette mode, suppressing phonabet input"), LocalizedStringKey("Enable cassette mode, suppressing phonabet input"),
isOn: $cassetteEnabled.onChange { isOn: $cassetteEnabled.onChange {
if cassetteEnabled, !LMMgr.checkCassettePathValidity(cassettePath) { if cassetteEnabled, !LMMgr.checkCassettePathValidity(cassettePath) {
if let window = CtlPrefUIShared.sharedWindow { if let window = CtlPrefUI.shared?.window {
IMEApp.buzz() IMEApp.buzz()
let alert = NSAlert(error: NSLocalizedString("Path invalid or file access error.", comment: "")) let alert = NSAlert(error: NSLocalizedString("Path invalid or file access error.", comment: ""))
alert.informativeText = NSLocalizedString( alert.informativeText = NSLocalizedString(
@ -186,7 +185,7 @@ struct VwrPrefPaneCassette: View {
.settingsDescription() .settingsDescription()
} }
} }
}.formStyled().frame(width: CtlPrefUIShared.formWidth) }.formStyled().frame(minWidth: CtlPrefUIShared.formWidth, maxWidth: ceil(CtlPrefUIShared.formWidth * 1.2))
} }
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight) .frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
} }

View File

@ -7,7 +7,6 @@
// requirements defined in MIT License. // requirements defined in MIT License.
import Shared import Shared
import SSPreferences
import SwiftExtension import SwiftExtension
import SwiftUI import SwiftUI
import SwiftUIBackports import SwiftUIBackports
@ -57,7 +56,7 @@ struct VwrPrefPaneDevZone: View {
.settingsDescription() .settingsDescription()
} }
} }
}.formStyled().frame(width: CtlPrefUIShared.formWidth) }.formStyled().frame(minWidth: CtlPrefUIShared.formWidth, maxWidth: ceil(CtlPrefUIShared.formWidth * 1.2))
} }
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight) .frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
} }

View File

@ -10,7 +10,6 @@ import BookmarkManager
import CocoaExtension import CocoaExtension
import MainAssembly import MainAssembly
import Shared import Shared
import SSPreferences
import SwiftExtension import SwiftExtension
import SwiftUI import SwiftUI
import SwiftUIBackports import SwiftUIBackports
@ -107,7 +106,7 @@ struct VwrPrefPaneDictionary: View {
let bolPreviousFolderValidity = LMMgr.checkIfSpecifiedUserDataFolderValid( let bolPreviousFolderValidity = LMMgr.checkIfSpecifiedUserDataFolderValid(
userDataFolderSpecified.expandingTildeInPath) userDataFolderSpecified.expandingTildeInPath)
if let window = CtlPrefUIShared.sharedWindow { if let window = CtlPrefUI.shared?.window {
Self.dlgOpenPath.beginSheetModal(for: window) { result in Self.dlgOpenPath.beginSheetModal(for: window) { result in
if result == NSApplication.ModalResponse.OK { if result == NSApplication.ModalResponse.OK {
guard let url = Self.dlgOpenPath.url else { return } guard let url = Self.dlgOpenPath.url else { return }
@ -233,7 +232,7 @@ struct VwrPrefPaneDictionary: View {
.settingsDescription() .settingsDescription()
} }
} }
}.formStyled().frame(width: CtlPrefUIShared.formWidth) }.formStyled().frame(minWidth: CtlPrefUIShared.formWidth, maxWidth: ceil(CtlPrefUIShared.formWidth * 1.2))
} }
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight) .frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
} }

View File

@ -8,7 +8,6 @@
import MainAssembly import MainAssembly
import Shared import Shared
import SSPreferences
import SwiftExtension import SwiftExtension
import SwiftUI import SwiftUI
import SwiftUIBackports import SwiftUIBackports
@ -144,7 +143,7 @@ struct VwrPrefPaneGeneral: View {
} }
} }
alert.addButton(withTitle: NSLocalizedString("Leave it checked", comment: "")) alert.addButton(withTitle: NSLocalizedString("Leave it checked", comment: ""))
if let window = CtlPrefUIShared.sharedWindow, !shouldNotFartInLieuOfBeep { if let window = CtlPrefUI.shared?.window, !shouldNotFartInLieuOfBeep {
shouldNotFartInLieuOfBeep = true shouldNotFartInLieuOfBeep = true
alert.beginSheetModal(for: window) { result in alert.beginSheetModal(for: window) { result in
switch result { switch result {
@ -176,7 +175,7 @@ struct VwrPrefPaneGeneral: View {
isOn: $isDebugModeEnabled isOn: $isDebugModeEnabled
) )
} }
}.formStyled().frame(width: CtlPrefUIShared.formWidth) }.formStyled().frame(minWidth: CtlPrefUIShared.formWidth, maxWidth: ceil(CtlPrefUIShared.formWidth * 1.2))
} }
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight) .frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
} }

View File

@ -9,7 +9,6 @@
import IMKUtils import IMKUtils
import MainAssembly import MainAssembly
import Shared import Shared
import SSPreferences
import SwiftExtension import SwiftExtension
import SwiftUI import SwiftUI
import SwiftUIBackports import SwiftUIBackports
@ -126,7 +125,7 @@ struct VwrPrefPaneKeyboard: View {
Section(header: Text("Keyboard Shortcuts:")) { Section(header: Text("Keyboard Shortcuts:")) {
VwrPrefPaneKeyboard_KeyboardShortcuts() VwrPrefPaneKeyboard_KeyboardShortcuts()
} }
}.formStyled().frame(width: CtlPrefUIShared.formWidth) }.formStyled().frame(minWidth: CtlPrefUIShared.formWidth, maxWidth: ceil(CtlPrefUIShared.formWidth * 1.2))
} }
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight) .frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
} }

View File

@ -7,7 +7,6 @@
// requirements defined in MIT License. // requirements defined in MIT License.
import Shared import Shared
import SSPreferences
import SwiftExtension import SwiftExtension
import SwiftUI import SwiftUI
import SwiftUIBackports import SwiftUIBackports
@ -74,7 +73,7 @@ struct VwrPrefPaneOutput: View {
.settingsDescription() .settingsDescription()
} }
} }
}.formStyled().frame(width: CtlPrefUIShared.formWidth) }.formStyled().frame(minWidth: CtlPrefUIShared.formWidth, maxWidth: ceil(CtlPrefUIShared.formWidth * 1.2))
} }
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight) .frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
} }

View File

@ -9,7 +9,6 @@
import MainAssembly import MainAssembly
import PhraseEditorUI import PhraseEditorUI
import Shared import Shared
import SSPreferences
import SwiftExtension import SwiftExtension
import SwiftUI import SwiftUI
@ -19,7 +18,7 @@ struct VwrPrefPanePhrases: View {
ScrollView { ScrollView {
VStack { VStack {
GroupBox { GroupBox {
VwrPhraseEditorUI(delegate: LMMgr.shared, window: CtlPrefUIShared.sharedWindow) VwrPhraseEditorUI(delegate: LMMgr.shared, window: CtlPrefUI.shared?.window)
.padding(4) .padding(4)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.frame(height: 440) .frame(height: 440)
@ -27,7 +26,7 @@ struct VwrPrefPanePhrases: View {
} }
.padding(4) .padding(4)
.padding() .padding()
.frame(width: CtlPrefUIShared.formWidth) .frame(minWidth: CtlPrefUIShared.formWidth, maxWidth: ceil(CtlPrefUIShared.formWidth * 1.2))
} }
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight) .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 MainAssembly
import Shared import Shared
enum PrefUITabs: String, CaseIterable { enum PrefUITabs: String, CaseIterable, Identifiable, Hashable {
var id: ObjectIdentifier { .init(rawValue as NSString) }
case tabGeneral = "General" case tabGeneral = "General"
case tabCandidates = "Candidates" case tabCandidates = "Candidates"
case tabBehavior = "Behavior" case tabBehavior = "Behavior"

View File

@ -124,7 +124,7 @@ class CtlPrefWindow: NSWindowController, NSWindowDelegate {
toolbar.delegate = self toolbar.delegate = self
toolbar.selectedItemIdentifier = PrefUITabs.tabGeneral.toolbarIdentifier toolbar.selectedItemIdentifier = PrefUITabs.tabGeneral.toolbarIdentifier
toolbar.showsBaselineSeparator = true toolbar.showsBaselineSeparator = true
if #available(macOS 11.0, *) { if #available(macOS 13, *) {
window?.toolbarStyle = .preference window?.toolbarStyle = .preference
} }
window?.toolbar = toolbar 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 */; }; 5B30BF282944867800BD87A9 /* CtlRevLookupWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B30BF272944867800BD87A9 /* CtlRevLookupWindow.swift */; };
5B40113928D7050D00A9D4CB /* Shared in Frameworks */ = {isa = PBXBuildFile; productRef = 5B40113828D7050D00A9D4CB /* Shared */; }; 5B40113928D7050D00A9D4CB /* Shared in Frameworks */ = {isa = PBXBuildFile; productRef = 5B40113828D7050D00A9D4CB /* Shared */; };
5B40113C28D71C0100A9D4CB /* Uninstaller in Frameworks */ = {isa = PBXBuildFile; productRef = 5B40113B28D71C0100A9D4CB /* Uninstaller */; }; 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 */; }; 5B5A603028E81CC50001AE8D /* SwiftUIBackports in Frameworks */ = {isa = PBXBuildFile; productRef = 5B5A602F28E81CC50001AE8D /* SwiftUIBackports */; };
5B62A33D27AE7CC100A19448 /* CtlAboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33C27AE7CC100A19448 /* CtlAboutWindow.swift */; }; 5B62A33D27AE7CC100A19448 /* CtlAboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33C27AE7CC100A19448 /* CtlAboutWindow.swift */; };
5B65FB342A9518DA007EEFB0 /* MainAssembly in Frameworks */ = {isa = PBXBuildFile; productRef = 5B65FB332A9518DA007EEFB0 /* MainAssembly */; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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; }; 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>"; }; 5B65B919284D0185007C558B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
@ -475,6 +477,7 @@
5B2E009328FD1E8100E78D6E /* VwrPrefPaneCassette.swift */, 5B2E009328FD1E8100E78D6E /* VwrPrefPaneCassette.swift */,
5BA9FD0B27FEDB6B002DE248 /* VwrPrefPaneKeyboard.swift */, 5BA9FD0B27FEDB6B002DE248 /* VwrPrefPaneKeyboard.swift */,
5B78EE0C28A562B4009456C1 /* VwrPrefPaneDevZone.swift */, 5B78EE0C28A562B4009456C1 /* VwrPrefPaneDevZone.swift */,
5B4C59AD2ABCB18700098626 /* VwrSettingsUI.swift */,
); );
path = PrefUI; path = PrefUI;
sourceTree = "<group>"; sourceTree = "<group>";
@ -984,6 +987,7 @@
5BA9FD1127FEDB6B002DE248 /* CtlPrefUIShared.swift in Sources */, 5BA9FD1127FEDB6B002DE248 /* CtlPrefUIShared.swift in Sources */,
5BA9FD1327FEDB6B002DE248 /* VwrPrefPaneDictionary.swift in Sources */, 5BA9FD1327FEDB6B002DE248 /* VwrPrefPaneDictionary.swift in Sources */,
5BF7548929B2F04F00FA50DA /* CtlPrefUI.swift in Sources */, 5BF7548929B2F04F00FA50DA /* CtlPrefUI.swift in Sources */,
5B4C59AE2ABCB18700098626 /* VwrSettingsUI.swift in Sources */,
5BF018F9299923BD00248CDD /* VwrPrefPaneBehavior.swift in Sources */, 5BF018F9299923BD00248CDD /* VwrPrefPaneBehavior.swift in Sources */,
5BB802DA27FABA8300CF1C19 /* SessionCtl_Menu.swift in Sources */, 5BB802DA27FABA8300CF1C19 /* SessionCtl_Menu.swift in Sources */,
5B2E009428FD1E8100E78D6E /* VwrPrefPaneCassette.swift in Sources */, 5B2E009428FD1E8100E78D6E /* VwrPrefPaneCassette.swift in Sources */,