From a219b7881ff69d176ac6dd35bad0a817f625f511 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Thu, 8 Feb 2024 22:51:07 +0800 Subject: [PATCH] AboutUI // Reimplement without XIB for old systems. --- .../MainAssembly/AboutUI/CtlAboutUI.swift | 53 ++++- .../MainAssembly/AboutUI/VwrAboutCocoa.swift | 211 ++++++++++++++++++ .../MainAssembly/AboutUI/VwrAboutUI.swift | 14 +- 3 files changed, 266 insertions(+), 12 deletions(-) create mode 100644 Packages/vChewing_MainAssembly/Sources/MainAssembly/AboutUI/VwrAboutCocoa.swift diff --git a/Packages/vChewing_MainAssembly/Sources/MainAssembly/AboutUI/CtlAboutUI.swift b/Packages/vChewing_MainAssembly/Sources/MainAssembly/AboutUI/CtlAboutUI.swift index 44595032..3f9713f5 100644 --- a/Packages/vChewing_MainAssembly/Sources/MainAssembly/AboutUI/CtlAboutUI.swift +++ b/Packages/vChewing_MainAssembly/Sources/MainAssembly/AboutUI/CtlAboutUI.swift @@ -9,21 +9,38 @@ import AppKit import SwiftUI -@available(macOS 12, *) public class CtlAboutUI: NSWindowController, NSWindowDelegate { public static var shared: CtlAboutUI? + private var viewController: NSViewController? + var useLegacyView: Bool = false + + public init(forceLegacy: Bool = false) { + useLegacyView = forceLegacy + let newWindow = NSWindow( + contentRect: CGRect(x: 401, y: 295, width: 577, height: 568), + styleMask: [.titled, .closable, .miniaturizable], + backing: .buffered, defer: true + ) + super.init(window: newWindow) + guard #available(macOS 12, *), !useLegacyView else { + viewController = VwrAboutCocoa() + viewController?.loadView() + return + } + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } public static func show() { + let forceLegacy = NSEvent.modifierFlags == .option if shared == nil { - let newWindow = NSWindow( - contentRect: CGRect(x: 401, y: 295, width: 577, height: 568), - styleMask: [.titled, .closable, .miniaturizable], - backing: .buffered, defer: true - ) - let newInstance = CtlAboutUI(window: newWindow) + let newInstance = CtlAboutUI(forceLegacy: forceLegacy) shared = newInstance } guard let shared = shared, let sharedWindow = shared.window else { return } + shared.useLegacyView = forceLegacy sharedWindow.delegate = shared if !sharedWindow.isVisible { shared.windowDidLoad() @@ -37,6 +54,28 @@ public class CtlAboutUI: NSWindowController, NSWindowDelegate { override public func windowDidLoad() { super.windowDidLoad() + guard let window = window else { return } + if #available(macOS 12, *), !useLegacyView { + windowDidLoadSwiftUI() + return + } + let theViewController = viewController ?? VwrAboutCocoa() + viewController = theViewController + window.contentViewController = viewController + let size = theViewController.view.fittingSize + window.setPosition(vertical: .top, horizontal: .left, padding: 20) + window.setFrame(.init(origin: window.frame.origin, size: size), display: true) + window.standardWindowButton(.closeButton)?.isHidden = true + window.standardWindowButton(.miniaturizeButton)?.isHidden = true + window.standardWindowButton(.zoomButton)?.isHidden = true + if #available(macOS 10.10, *) { + window.titlebarAppearsTransparent = true + } + window.title = "i18n:aboutWindow.ABOUT_APP_TITLE_FULL".localized + " (v\(IMEApp.appMainVersionLabel.joined(separator: " Build ")))" + } + + @available(macOS 12, *) + private func windowDidLoadSwiftUI() { window?.setPosition(vertical: .top, horizontal: .left, padding: 20) window?.standardWindowButton(.closeButton)?.isHidden = true window?.standardWindowButton(.miniaturizeButton)?.isHidden = true diff --git a/Packages/vChewing_MainAssembly/Sources/MainAssembly/AboutUI/VwrAboutCocoa.swift b/Packages/vChewing_MainAssembly/Sources/MainAssembly/AboutUI/VwrAboutCocoa.swift new file mode 100644 index 00000000..427fe325 --- /dev/null +++ b/Packages/vChewing_MainAssembly/Sources/MainAssembly/AboutUI/VwrAboutCocoa.swift @@ -0,0 +1,211 @@ +// (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 AppKit +import Foundation +import Shared + +public extension VwrAboutCocoa { + static let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] as? String ?? "BAD_COPYRIGHT_LABEL" + static let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String ?? "BAD_EULA_CONTENT" + static let eulaContentUpstream = Bundle.main.infoDictionary?["CFUpstreamEULAContent"] as? String ?? "BAD_EULA_UPSTREAM" +} + +public class VwrAboutCocoa: NSViewController { + let windowWidth: CGFloat = 533 + let contentWidth: CGFloat = 510 + let imgWidth: CGFloat = 63 + + override public func loadView() { + view = body ?? .init() + (view as? NSStackView)?.alignment = .centerX + view.makeSimpleConstraint(.width, relation: .equal, value: windowWidth) + } + + var appNameAndVersionString: NSAttributedString { + let strResult = NSMutableAttributedString(string: "i18n:aboutWindow.APP_NAME".localized) + strResult.addAttribute( + .font, + value: { + if #available(macOS 10.11, *) { + return NSFont.systemFont(ofSize: 12, weight: .bold) + } + return NSFont.boldSystemFont(ofSize: 12) + }(), + range: .init(location: 0, length: strResult.length) + ) + let strVersion = NSMutableAttributedString(string: " \(versionString)") + strVersion.addAttribute( + .font, + value: NSFont.systemFont(ofSize: 11), + range: .init(location: 0, length: strVersion.length) + ) + strResult.append(strVersion) + strResult.addAttribute( + .kern, + value: 0, + range: .init(location: 0, length: strResult.length) + ) + return strResult + } + + var body: NSView? { + NSStackView.buildSection(width: contentWidth - 18) { + NSStackView.build(.horizontal) { + bannerImage + NSStackView.build(.vertical) { + appNameAndVersionString.makeNSLabel(fixWidth: contentWidth - imgWidth - 10) + makeFormattedLabel( + verbatim: "i18n:aboutWindow.APP_DERIVED_FROM".localized + + "\n" + + Self.copyrightLabel, + size: 11, + isBold: false, fixWidth: contentWidth - imgWidth - 10 + ) + makeFormattedLabel( + verbatim: "i18n:aboutWindow.DEV_CREW".localized, + size: 11, + isBold: false, fixWidth: contentWidth - imgWidth - 10 + ) + makeFormattedLabel( + verbatim: "i18n:aboutWindow.LICENSE_TITLE".localized, + size: 11, + isBold: false, fixWidth: contentWidth - imgWidth - 10 + ) + eulaBox + } + } + NSStackView.build(.horizontal) { + NSStackView.build(.vertical) { + "i18n:aboutWindow.DISCLAIMER_TEXT".makeNSLabel( + descriptive: true, fixWidth: contentWidth - 120 + ) + NSView() + } + NSStackView.build(.vertical, width: 114) { + addKeyEquivalent( + NSButton( + "i18n:aboutWindow.OK_BUTTON", + target: self, action: #selector(btnOKAction(_:)) + ) + ) + NSButton( + "i18n:aboutWindow.WEBSITE_BUTTON", + target: self, action: #selector(btnWebSiteAction(_:)) + ) + NSButton( + "i18n:aboutWindow.BUGREPORT_BUTTON", + target: self, action: #selector(btnBugReportAction(_:)) + ) + } + } + }?.withInsets( + { + if #available(macOS 10.10, *) { + return .new(all: 20, top: 0, bottom: 24) + } else { + return .new(all: 20, top: 10, bottom: 24) + } + }() + ) + } + + var versionString: String { + "v\(IMEApp.appMainVersionLabel.joined(separator: " Build ")) - \(IMEApp.appSignedDateLabel)" + } + + var bannerImage: NSImageView { + let maybeImg = NSImage(named: "AboutBanner") + let imgIsNull = maybeImg == nil + let img = maybeImg ?? .init(size: .init(width: 63, height: 310)) + let result = NSImageView() + result.image = img + result.makeSimpleConstraint(.width, relation: .equal, value: 63) + result.makeSimpleConstraint(.height, relation: .equal, value: 310) + if imgIsNull { + result.wantsLayer = true + result.layer?.backgroundColor = NSColor.black.cgColor + } + return result + } + + func makeFormattedLabel( + verbatim: String, + size: CGFloat = 12, + isBold: Bool = false, + fixWidth: CGFloat? = nil + ) -> NSTextField { + let attrStr = NSMutableAttributedString(string: verbatim) + attrStr.addAttribute( + .kern, + value: 0, + range: .init(location: 0, length: attrStr.length) + ) + attrStr.addAttribute( + .font, + value: { + guard isBold else { return NSFont.systemFont(ofSize: size) } + if #available(macOS 10.11, *) { + return NSFont.systemFont(ofSize: size, weight: .bold) + } + return NSFont.boldSystemFont(ofSize: size) + }(), + range: .init(location: 0, length: attrStr.length) + ) + return attrStr.makeNSLabel(fixWidth: fixWidth) + } + + var eulaBox: NSScrollView { + let textView = NSTextView() + let clipView = NSClipView() + let scrollView = NSScrollView() + textView.autoresizingMask = [.width, .height] + textView.isEditable = false + textView.isRichText = false + textView.isSelectable = true + textView.isVerticallyResizable = true + textView.smartInsertDeleteEnabled = true + textView.font = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize) + textView.string = Self.eulaContent + "\n" + Self.eulaContentUpstream + clipView.documentView = textView + clipView.autoresizingMask = [.width, .height] + clipView.drawsBackground = false + scrollView.contentView = clipView + scrollView.makeSimpleConstraint(.width, relation: .equal, value: 430) + scrollView.hasVerticalScroller = true + scrollView.hasHorizontalScroller = false + scrollView.scrollerStyle = .legacy + return scrollView + } + + @discardableResult func addKeyEquivalent(_ button: NSButton) -> NSButton { + button.keyEquivalent = String(NSEvent.SpecialKey.carriageReturn.unicodeScalar) + return button + } + + @objc func btnOKAction(_: NSControl) { + CtlAboutUI.shared?.window?.close() + } + + @objc func btnWebSiteAction(_: NSControl) { + if let url = URL(string: "https://vchewing.github.io/") { + NSWorkspace.shared.open(url) + } + } + + @objc func btnBugReportAction(_: NSControl) { + if let url = URL(string: "https://vchewing.github.io/BUGREPORT.html") { + NSWorkspace.shared.open(url) + } + } +} + +@available(macOS 14.0, *) +#Preview(traits: .fixedLayout(width: 533, height: 550)) { + VwrAboutCocoa() +} diff --git a/Packages/vChewing_MainAssembly/Sources/MainAssembly/AboutUI/VwrAboutUI.swift b/Packages/vChewing_MainAssembly/Sources/MainAssembly/AboutUI/VwrAboutUI.swift index 80a6f4da..92d9c81a 100644 --- a/Packages/vChewing_MainAssembly/Sources/MainAssembly/AboutUI/VwrAboutUI.swift +++ b/Packages/vChewing_MainAssembly/Sources/MainAssembly/AboutUI/VwrAboutUI.swift @@ -9,12 +9,15 @@ import AppKit import SwiftUI -@available(macOS 12, *) -public struct VwrAboutUI: View { - public static let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] as? String ?? "BAD_COPYRIGHT_LABEL" - public static let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String ?? "BAD_EULA_CONTENT" - public static let eulaContentUpstream = Bundle.main.infoDictionary?["CFUpstreamEULAContent"] as? String ?? "BAD_EULA_UPSTREAM" +public struct VwrAboutUI { + public static var copyrightLabel: String { VwrAboutCocoa.copyrightLabel } + public static var eulaContent: String { VwrAboutCocoa.eulaContent } + public static var eulaContentUpstream: String { VwrAboutCocoa.eulaContentUpstream } + let foobar = "FOO_BAR" +} +@available(macOS 12, *) +extension VwrAboutUI: View { public var body: some View { GroupBox { VStack(alignment: .leading, spacing: 6) { @@ -29,6 +32,7 @@ public struct VwrAboutUI: View { Text("v\(IMEApp.appMainVersionLabel.joined(separator: " Build ")) - \(IMEApp.appSignedDateLabel)").lineLimit(1) }.fixedSize() Text("i18n:aboutWindow.APP_DERIVED_FROM").font(.custom("Tahoma", size: 11)) + Text(Self.copyrightLabel).font(.custom("Tahoma", size: 11)) Text("i18n:aboutWindow.DEV_CREW").font(.custom("Tahoma", size: 11)).padding([.vertical], 2) } }