AboutUI // Reimplement without XIB for old systems.
This commit is contained in:
parent
80fe5ecb74
commit
a219b7881f
|
@ -9,21 +9,38 @@
|
||||||
import AppKit
|
import AppKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@available(macOS 12, *)
|
|
||||||
public class CtlAboutUI: NSWindowController, NSWindowDelegate {
|
public class CtlAboutUI: NSWindowController, NSWindowDelegate {
|
||||||
public static var shared: CtlAboutUI?
|
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() {
|
public static func show() {
|
||||||
|
let forceLegacy = NSEvent.modifierFlags == .option
|
||||||
if shared == nil {
|
if shared == nil {
|
||||||
let newWindow = NSWindow(
|
let newInstance = CtlAboutUI(forceLegacy: forceLegacy)
|
||||||
contentRect: CGRect(x: 401, y: 295, width: 577, height: 568),
|
|
||||||
styleMask: [.titled, .closable, .miniaturizable],
|
|
||||||
backing: .buffered, defer: true
|
|
||||||
)
|
|
||||||
let newInstance = CtlAboutUI(window: newWindow)
|
|
||||||
shared = newInstance
|
shared = newInstance
|
||||||
}
|
}
|
||||||
guard let shared = shared, let sharedWindow = shared.window else { return }
|
guard let shared = shared, let sharedWindow = shared.window else { return }
|
||||||
|
shared.useLegacyView = forceLegacy
|
||||||
sharedWindow.delegate = shared
|
sharedWindow.delegate = shared
|
||||||
if !sharedWindow.isVisible {
|
if !sharedWindow.isVisible {
|
||||||
shared.windowDidLoad()
|
shared.windowDidLoad()
|
||||||
|
@ -37,6 +54,28 @@ public class CtlAboutUI: NSWindowController, NSWindowDelegate {
|
||||||
|
|
||||||
override public func windowDidLoad() {
|
override public func windowDidLoad() {
|
||||||
super.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?.setPosition(vertical: .top, horizontal: .left, padding: 20)
|
||||||
window?.standardWindowButton(.closeButton)?.isHidden = true
|
window?.standardWindowButton(.closeButton)?.isHidden = true
|
||||||
window?.standardWindowButton(.miniaturizeButton)?.isHidden = true
|
window?.standardWindowButton(.miniaturizeButton)?.isHidden = true
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
|
@ -9,12 +9,15 @@
|
||||||
import AppKit
|
import AppKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@available(macOS 12, *)
|
public struct VwrAboutUI {
|
||||||
public struct VwrAboutUI: View {
|
public static var copyrightLabel: String { VwrAboutCocoa.copyrightLabel }
|
||||||
public static let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] as? String ?? "BAD_COPYRIGHT_LABEL"
|
public static var eulaContent: String { VwrAboutCocoa.eulaContent }
|
||||||
public static let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String ?? "BAD_EULA_CONTENT"
|
public static var eulaContentUpstream: String { VwrAboutCocoa.eulaContentUpstream }
|
||||||
public static let eulaContentUpstream = Bundle.main.infoDictionary?["CFUpstreamEULAContent"] as? String ?? "BAD_EULA_UPSTREAM"
|
let foobar = "FOO_BAR"
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(macOS 12, *)
|
||||||
|
extension VwrAboutUI: View {
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
GroupBox {
|
GroupBox {
|
||||||
VStack(alignment: .leading, spacing: 6) {
|
VStack(alignment: .leading, spacing: 6) {
|
||||||
|
@ -29,6 +32,7 @@ public struct VwrAboutUI: View {
|
||||||
Text("v\(IMEApp.appMainVersionLabel.joined(separator: " Build ")) - \(IMEApp.appSignedDateLabel)").lineLimit(1)
|
Text("v\(IMEApp.appMainVersionLabel.joined(separator: " Build ")) - \(IMEApp.appSignedDateLabel)").lineLimit(1)
|
||||||
}.fixedSize()
|
}.fixedSize()
|
||||||
Text("i18n:aboutWindow.APP_DERIVED_FROM").font(.custom("Tahoma", size: 11))
|
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)
|
Text("i18n:aboutWindow.DEV_CREW").font(.custom("Tahoma", size: 11)).padding([.vertical], 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue