CtlCandidateTDK // Rewrite.

This commit is contained in:
ShikiSuen 2023-07-16 17:28:10 +08:00
parent e4a8f34075
commit 339cfb0ad4
7 changed files with 859 additions and 398 deletions

View File

@ -8,18 +8,20 @@
import AppKit import AppKit
import Shared import Shared
import SwiftUI
import SwiftUIBackports
// MARK: - Candidate Cell // MARK: - Candidate Cell
/// class 便 /// class 便
public class CandidateCellData: Hashable { public class CandidateCellData: Hashable {
public var visualDimension: CGSize = .zero public var visualDimension: CGSize = .zero
public var visualOrigin: CGPoint = .zero
public var locale = "" public var locale = ""
public static var unifiedSize: Double = 16 public static var unifiedSize: Double = 16
public static var unifiedCharDimension: Double { ceil(unifiedSize * 1.0125 + 7) }
public static var unifiedTextHeight: Double { ceil(unifiedSize * 19 / 16) }
public var selectionKey: String public var selectionKey: String
public var displayedText: String public let displayedText: String
public private(set) var textDimension: NSSize
public var spanLength: Int public var spanLength: Int
public var size: Double { Self.unifiedSize } public var size: Double { Self.unifiedSize }
public var isHighlighted: Bool = false public var isHighlighted: Bool = false
@ -29,7 +31,6 @@ public class CandidateCellData: Hashable {
// / // /
public var subIndex: Int = 0 public var subIndex: Int = 0
public var charGlyphWidth: Double { ceil(size * 1.0125 + 7) }
public var fontSizeCandidate: Double { size } public var fontSizeCandidate: Double { size }
public var fontSizeKey: Double { max(ceil(fontSizeCandidate * 0.6), 11) } public var fontSizeKey: Double { max(ceil(fontSizeCandidate * 0.6), 11) }
public var fontColorCandidate: NSColor { isHighlighted ? .selectedMenuItemTextColor : .controlTextColor } public var fontColorCandidate: NSColor { isHighlighted ? .selectedMenuItemTextColor : .controlTextColor }
@ -64,6 +65,10 @@ public class CandidateCellData: Hashable {
self.displayedText = displayedText self.displayedText = displayedText
spanLength = max(spanningLength ?? displayedText.count, 1) spanLength = max(spanningLength ?? displayedText.count, 1)
isHighlighted = isSelected isHighlighted = isSelected
textDimension = .init(width: ceil(Self.unifiedCharDimension * 1.4), height: Self.unifiedTextHeight)
if displayedText.count > 1 {
textDimension.width = attributedString().boundingDimension.width
}
} }
public static func == (lhs: CandidateCellData, rhs: CandidateCellData) -> Bool { public static func == (lhs: CandidateCellData, rhs: CandidateCellData) -> Bool {
@ -76,9 +81,9 @@ public class CandidateCellData: Hashable {
} }
public func cellLength(isMatrix: Bool = true) -> Double { public func cellLength(isMatrix: Bool = true) -> Double {
let minLength = ceil(charGlyphWidth * 2 + size * 1.25) let minLength = ceil(Self.unifiedCharDimension * 2 + size * 1.25)
if displayedText.count <= 2, isMatrix { return minLength } if displayedText.count <= 2, isMatrix { return minLength }
return ceil(attributedStringPhrase().boundingDimension.width + charGlyphWidth) return textDimension.width
} }
// MARK: - Fonts and NSColors. // MARK: - Fonts and NSColors.
@ -202,6 +207,20 @@ public class CandidateCellData: Hashable {
return String(format: "U+%02X %@", $0.value, theName) return String(format: "U+%02X %@", $0.value, theName)
} }
} }
public func updateMetrics(pool thePool: CandidatePool, origin currentOrigin: CGPoint) {
let padding = thePool.padding
var cellDimension = textDimension
if let givenWidth = thePool.cellWidth(self).min, displayedText.count <= 2 {
cellDimension.width = max(cellDimension.width + 4 * padding, givenWidth)
} else {
cellDimension.width += 4 * padding
}
cellDimension.width = ceil(cellDimension.width)
cellDimension.height = Self.unifiedTextHeight + 2 * padding
visualDimension = cellDimension
visualOrigin = currentOrigin
}
} }
// MARK: - Array Container Extension. // MARK: - Array Container Extension.

View File

@ -23,12 +23,33 @@ public class CandidatePool {
public var reverseLookupResult: [String] = [] public var reverseLookupResult: [String] = []
public private(set) var highlightedIndex: Int = 0 public private(set) var highlightedIndex: Int = 0
public private(set) var currentLineNumber = 0 public private(set) var currentLineNumber = 0
public var metrics: UIMetrics = .allZeroed
private var recordedLineRangeForCurrentPage: Range<Int>? private var recordedLineRangeForCurrentPage: Range<Int>?
private var previouslyRecordedLineRangeForPreviousPage: Range<Int>? private var previouslyRecordedLineRangeForPreviousPage: Range<Int>?
public struct UIMetrics {
static var allZeroed: UIMetrics {
.init(fittingSize: .zero, highlightedLine: .zero, highlightedCandidate: .zero, peripherals: .zero)
}
let fittingSize: CGSize
let highlightedLine: CGRect
let highlightedCandidate: CGRect
let peripherals: CGRect
}
// MARK: - // MARK: -
public let padding: CGFloat = 2
public let originDelta: CGFloat = 5
public let cellTextHeight = CandidatePool.shitCell.textDimension.height
public let cellRadius: CGFloat = 4
public var windowRadius: CGFloat { originDelta + cellRadius }
/// /
public var isMatrix: Bool { maxLinesPerPage > 1 }
/// ///
/// ///
public var maxRowWidth: Double { ceil(Double(maxLineCapacity) * Self.blankCell.cellLength()) } public var maxRowWidth: Double { ceil(Double(maxLineCapacity) * Self.blankCell.cellLength()) }
@ -130,6 +151,7 @@ public class CandidatePool {
candidateLines.append(currentColumn) candidateLines.append(currentColumn)
recordedLineRangeForCurrentPage = fallbackedLineRangeForCurrentPage recordedLineRangeForCurrentPage = fallbackedLineRangeForCurrentPage
highlight(at: 0) highlight(at: 0)
updateMetrics()
} }
} }
@ -254,7 +276,7 @@ public extension CandidatePool {
if layout != .vertical, maxLinesPerPage == 1 { if layout != .vertical, maxLinesPerPage == 1 {
min = max(minAccepted, cell.cellLength(isMatrix: false)) min = max(minAccepted, cell.cellLength(isMatrix: false))
} else if layout == .vertical, maxLinesPerPage == 1 { } else if layout == .vertical, maxLinesPerPage == 1 {
min = max(Double(CandidateCellData.unifiedSize * 6), 90) min = max(Double(CandidateCellData.unifiedSize * 6), ceil(cell.size * 5.6))
} }
return (min, nil) return (min, nil)
} }

View File

@ -8,7 +8,141 @@
import AppKit import AppKit
// MARK: - Using One Single NSAttributedString. // MARK: - UI Metrics.
extension CandidatePool {
public func updateMetrics() {
//
let initialOrigin: NSPoint = .init(x: originDelta, y: originDelta)
var totalAccuSize: NSSize = .zero
// Origin is at the top-left corner.
var currentOrigin: NSPoint = initialOrigin
var highlightedCellRect: CGRect = .zero
var highlightedLineRect: CGRect = .zero
var currentPageLines = candidateLines[lineRangeForCurrentPage]
var blankLines = maxLinesPerPage - currentPageLines.count
var fillBlankCells = true
switch (layout, isMatrix) {
case (.horizontal, false):
blankLines = 0
fillBlankCells = false
case (.vertical, false): blankLines = 0
case (_, true): break
}
while blankLines > 0 {
currentPageLines.append(.init(repeating: Self.shitCell, count: maxLineCapacity))
blankLines -= 1
}
Self.shitCell.updateMetrics(pool: self, origin: currentOrigin)
Self.shitCell.isHighlighted = false
let minimumCellDimension = Self.shitCell.visualDimension
currentPageLines.forEach { currentLine in
let currentLineOrigin = currentOrigin
var accumulatedLineSize: NSSize = .zero
var currentLineRect: CGRect { .init(origin: currentLineOrigin, size: accumulatedLineSize) }
let lineHasHighlightedCell = currentLine.hasHighlightedCell
currentLine.forEach { currentCell in
currentCell.updateMetrics(pool: self, origin: currentOrigin)
var cellDimension = currentCell.visualDimension
if layout == .vertical || currentCell.displayedText.count <= 2 {
cellDimension.width = max(minimumCellDimension.width, cellDimension.width)
}
cellDimension.height = max(minimumCellDimension.height, cellDimension.height)
switch self.layout {
case .horizontal:
accumulatedLineSize.width += cellDimension.width
accumulatedLineSize.height = max(accumulatedLineSize.height, cellDimension.height)
case .vertical:
accumulatedLineSize.height += cellDimension.height
accumulatedLineSize.width = max(accumulatedLineSize.width, cellDimension.width)
}
if lineHasHighlightedCell {
switch self.layout {
case .horizontal where currentCell.isHighlighted: highlightedCellRect.size.width = cellDimension.width
case .vertical: highlightedCellRect.size.width = max(highlightedCellRect.size.width, cellDimension.width)
default: break
}
if currentCell.isHighlighted {
highlightedCellRect.origin = currentOrigin
highlightedCellRect.size.height = cellDimension.height
}
}
switch self.layout {
case .horizontal: currentOrigin.x += cellDimension.width
case .vertical: currentOrigin.y += cellDimension.height
}
}
if lineHasHighlightedCell {
highlightedLineRect.origin = currentLineRect.origin
switch self.layout {
case .horizontal:
highlightedLineRect.size.height = currentLineRect.size.height
case .vertical:
highlightedLineRect.size.width = currentLineRect.size.width
}
}
switch self.layout {
case .horizontal:
highlightedLineRect.size.width = max(currentLineRect.size.width, highlightedLineRect.width)
case .vertical:
highlightedLineRect.size.height = max(currentLineRect.size.height, highlightedLineRect.height)
currentLine.forEach { theCell in
theCell.visualDimension.width = accumulatedLineSize.width
}
}
//
switch self.layout {
case .horizontal:
currentOrigin.x = originDelta
currentOrigin.y += accumulatedLineSize.height
totalAccuSize.width = max(totalAccuSize.width, accumulatedLineSize.width)
totalAccuSize.height += accumulatedLineSize.height
case .vertical:
currentOrigin.y = originDelta
currentOrigin.x += accumulatedLineSize.width
totalAccuSize.height = max(totalAccuSize.height, accumulatedLineSize.height)
totalAccuSize.width += accumulatedLineSize.width
}
}
if fillBlankCells {
switch layout {
case .horizontal:
totalAccuSize.width = max(totalAccuSize.width, CGFloat(maxLineCapacity) * minimumCellDimension.width)
highlightedLineRect.size.width = totalAccuSize.width
case .vertical:
totalAccuSize.height = CGFloat(maxLineCapacity) * minimumCellDimension.height
}
}
//
let strPeripherals = attributedDescriptionBottomPanes
var dimensionPeripherals = strPeripherals.boundingDimension
dimensionPeripherals.width = ceil(dimensionPeripherals.width)
dimensionPeripherals.height = ceil(dimensionPeripherals.height)
if finalContainerOrientation == .horizontal {
totalAccuSize.width += 5
dimensionPeripherals.width += 5
let delta = max(CandidateCellData.unifiedTextHeight + padding * 2 - dimensionPeripherals.height, 0)
currentOrigin = .init(x: totalAccuSize.width + originDelta, y: ceil(delta / 2) + originDelta)
totalAccuSize.width += dimensionPeripherals.width
} else {
totalAccuSize.height += 2
currentOrigin = .init(x: padding + originDelta, y: totalAccuSize.height + originDelta)
totalAccuSize.height += dimensionPeripherals.height
totalAccuSize.width = max(totalAccuSize.width, dimensionPeripherals.width)
}
let rectPeripherals = CGRect(origin: currentOrigin, size: dimensionPeripherals)
totalAccuSize.width += originDelta * 2
totalAccuSize.height += originDelta * 2
metrics = .init(fittingSize: totalAccuSize, highlightedLine: highlightedLineRect, highlightedCandidate: highlightedCellRect, peripherals: rectPeripherals)
}
private var finalContainerOrientation: NSUserInterfaceLayoutOrientation {
if maxLinesPerPage == 1, layout == .horizontal { return .horizontal }
return .vertical
}
}
// MARK: - Using One Single NSAttributedString. (Some of them are for debug purposes.)
extension CandidatePool { extension CandidatePool {
// MARK: Candidate List with Peripherals. // MARK: Candidate List with Peripherals.
@ -143,6 +277,7 @@ extension CandidatePool {
let positionCounterTextSize = max(ceil(CandidateCellData.unifiedSize * 0.7), 11) let positionCounterTextSize = max(ceil(CandidateCellData.unifiedSize * 0.7), 11)
let attrTooltip: [NSAttributedString.Key: AnyObject] = [ let attrTooltip: [NSAttributedString.Key: AnyObject] = [
.font: Self.blankCell.phraseFontEmphasized(size: positionCounterTextSize), .font: Self.blankCell.phraseFontEmphasized(size: positionCounterTextSize),
.foregroundColor: NSColor.textColor,
] ]
let tooltipText = NSAttributedString( let tooltipText = NSAttributedString(
string: " \(tooltip) ", attributes: attrTooltip string: " \(tooltip) ", attributes: attrTooltip
@ -154,6 +289,7 @@ extension CandidatePool {
let reverseLookupTextSize = max(ceil(CandidateCellData.unifiedSize * 0.6), 9) let reverseLookupTextSize = max(ceil(CandidateCellData.unifiedSize * 0.6), 9)
let attrReverseLookup: [NSAttributedString.Key: AnyObject] = [ let attrReverseLookup: [NSAttributedString.Key: AnyObject] = [
.font: Self.blankCell.phraseFont(size: reverseLookupTextSize), .font: Self.blankCell.phraseFont(size: reverseLookupTextSize),
.foregroundColor: NSColor.textColor,
] ]
let attrReverseLookupSpacer: [NSAttributedString.Key: AnyObject] = [ let attrReverseLookupSpacer: [NSAttributedString.Key: AnyObject] = [
.font: Self.blankCell.phraseFont(size: reverseLookupTextSize), .font: Self.blankCell.phraseFont(size: reverseLookupTextSize),

View File

@ -41,9 +41,16 @@ public class CtlCandidateTDK: CtlCandidate, NSWindowDelegate {
).edgesIgnoringSafeArea(.top) ).edgesIgnoringSafeArea(.top)
} }
#if USING_STACK_VIEW_IN_TDK_COCOA
///
private var theViewCocoa: NSStackView { private var theViewCocoa: NSStackView {
VwrCandidateTDKCocoa(controller: self, thePool: Self.thePool) VwrCandidateTDKCocoa(controller: self, thePool: Self.thePool)
} }
#endif
private var theViewAppKit: NSView {
VwrCandidateTDKAppKit(controller: self, thePool: Self.thePool)
}
private var theViewLegacy: NSView { private var theViewLegacy: NSView {
let textField = NSTextField() let textField = NSTextField()
@ -120,7 +127,7 @@ public class CtlCandidateTDK: CtlCandidate, NSWindowDelegate {
Self.currentView = NSHostingView(rootView: theView) Self.currentView = NSHostingView(rootView: theView)
break viewCheck break viewCheck
} }
Self.currentView = theViewCocoa Self.currentView = theViewAppKit
} }
window.contentView = Self.currentView window.contentView = Self.currentView
window.setContentSize(Self.currentView.fittingSize) window.setContentSize(Self.currentView.fittingSize)

View File

@ -0,0 +1,246 @@
// (c) 2022 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 Shared
/// AppKit SwiftUI
/// 使
public class VwrCandidateTDKAppKit: NSView {
public weak var controller: CtlCandidateTDK?
public var thePool: CandidatePool
private var dimension: NSSize = .zero
var action: Selector?
weak var target: AnyObject?
var theMenu: NSMenu?
var clickedCell: CandidateCellData = CandidatePool.shitCell
// MARK: - Variables used for rendering the UI.
var padding: CGFloat { thePool.padding }
var originDelta: CGFloat { thePool.originDelta }
var cellRadius: CGFloat { thePool.cellRadius }
var windowRadius: CGFloat { thePool.windowRadius }
var isMatrix: Bool { thePool.isMatrix }
// MARK: - Constructors.
public init(controller: CtlCandidateTDK? = nil, thePool pool: CandidatePool) {
self.controller = controller
thePool = pool
thePool.updateMetrics()
super.init(frame: .init(origin: .zero, size: .init(width: 114_514, height: 114_514)))
}
deinit {
theMenu?.cancelTrackingWithoutAnimation()
}
@available(*, unavailable)
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// MARK: - Interface Renderer (with shared public variables).
public extension VwrCandidateTDKAppKit {
override var isFlipped: Bool { true }
override var fittingSize: NSSize { thePool.metrics.fittingSize }
static var candidateListBackground: NSColor {
let delta = NSApplication.isDarkMode ? 0.05 : 0.99
return .init(white: delta, alpha: 1)
}
override func draw(_: NSRect) {
let sizesCalculated = thePool.metrics
//
let allRect = NSRect(origin: .zero, size: sizesCalculated.fittingSize)
Self.candidateListBackground.setFill()
NSBezierPath(roundedRect: allRect, xRadius: windowRadius, yRadius: windowRadius).fill()
//
lineBackground(isCurrentLine: true, isMatrix: isMatrix).setFill()
NSBezierPath(roundedRect: sizesCalculated.highlightedLine, xRadius: cellRadius, yRadius: cellRadius).fill()
var cellHighlightedDrawn = false
//
let allCells = thePool.candidateLines[thePool.lineRangeForCurrentPage].flatMap { $0 }
allCells.forEach { currentCell in
if currentCell.isHighlighted, !cellHighlightedDrawn {
currentCell.themeColorCocoa.setFill()
NSBezierPath(roundedRect: sizesCalculated.highlightedCandidate, xRadius: cellRadius, yRadius: cellRadius).fill()
cellHighlightedDrawn = true
}
currentCell.attributedStringHeader.draw(at:
.init(
x: currentCell.visualOrigin.x + 2 * padding,
y: currentCell.visualOrigin.y + ceil(currentCell.visualDimension.height * 0.2)
)
)
currentCell.attributedStringPhrase(isMatrix: false).draw(
at: .init(
x: currentCell.visualOrigin.x + 2 * padding + ceil(currentCell.size * 0.6),
y: currentCell.visualOrigin.y + padding
)
)
}
//
let strPeripherals = thePool.attributedDescriptionBottomPanes
strPeripherals.draw(at: sizesCalculated.peripherals.origin)
}
}
// MARK: - Mouse Interaction Handlers.
public extension VwrCandidateTDKAppKit {
private func findCell(from mouseEvent: NSEvent) -> Int? {
var clickPoint = convert(mouseEvent.locationInWindow, to: self)
clickPoint.y = bounds.height - clickPoint.y //
guard bounds.contains(clickPoint) else { return nil }
let flattenedCells = thePool.candidateLines[thePool.lineRangeForCurrentPage].flatMap { $0 }
let x = flattenedCells.filter { theCell in
NSPointInRect(clickPoint, .init(origin: theCell.visualOrigin, size: theCell.visualDimension))
}.first
guard let firstValidCell = x else { return nil }
return firstValidCell.index
}
override func mouseDown(with event: NSEvent) {
guard let cellIndex = findCell(from: event) else { return }
guard cellIndex != thePool.highlightedIndex else { return }
thePool.highlight(at: cellIndex)
thePool.updateMetrics()
setNeedsDisplay(bounds)
}
override func mouseDragged(with event: NSEvent) {
mouseDown(with: event)
}
override func mouseUp(with event: NSEvent) {
guard let cellIndex = findCell(from: event) else { return }
didSelectCandidateAt(cellIndex)
}
override func rightMouseUp(with event: NSEvent) {
guard let cellIndex = findCell(from: event) else { return }
clickedCell = thePool.candidateDataAll[cellIndex]
let index = clickedCell.index
let candidateText = clickedCell.displayedText
let isEnabled: Bool = controller?.delegate?.isCandidateContextMenuEnabled ?? false
guard isEnabled, !candidateText.isEmpty, index >= 0 else { return }
prepareMenu()
var clickPoint = convert(event.locationInWindow, to: self)
clickPoint.y = bounds.height - clickPoint.y //
theMenu?.popUp(positioning: nil, at: clickPoint, in: self)
}
}
// MARK: - Context Menu.
private extension VwrCandidateTDKAppKit {
private func prepareMenu() {
let newMenu = NSMenu()
let boostMenuItem = NSMenuItem(
title: "\(clickedCell.displayedText)",
action: #selector(menuActionOfBoosting(_:)),
keyEquivalent: ""
)
boostMenuItem.target = self
newMenu.addItem(boostMenuItem)
let nerfMenuItem = NSMenuItem(
title: "\(clickedCell.displayedText)",
action: #selector(menuActionOfNerfing(_:)),
keyEquivalent: ""
)
nerfMenuItem.target = self
newMenu.addItem(nerfMenuItem)
if thePool.isFilterable(target: clickedCell.index) {
let filterMenuItem = NSMenuItem(
title: "✖︎ \(clickedCell.displayedText)",
action: #selector(menuActionOfFiltering(_:)),
keyEquivalent: ""
)
filterMenuItem.target = self
newMenu.addItem(filterMenuItem)
}
theMenu = newMenu
CtlCandidateTDK.currentMenu = newMenu
}
@objc func menuActionOfBoosting(_: Any? = nil) {
didRightClickCandidateAt(clickedCell.index, action: .toBoost)
}
@objc func menuActionOfNerfing(_: Any? = nil) {
didRightClickCandidateAt(clickedCell.index, action: .toNerf)
}
@objc func menuActionOfFiltering(_: Any? = nil) {
didRightClickCandidateAt(clickedCell.index, action: .toFilter)
}
}
// MARK: - Delegate Methods
private extension VwrCandidateTDKAppKit {
func didSelectCandidateAt(_ pos: Int) {
controller?.delegate?.candidatePairSelectionConfirmed(at: pos)
}
func didRightClickCandidateAt(_ pos: Int, action: CandidateContextMenuAction) {
controller?.delegate?.candidatePairRightClicked(at: pos, action: action)
}
}
// MARK: - Extracted Internal Methods for UI Rendering.
private extension VwrCandidateTDKAppKit {
private func lineBackground(isCurrentLine: Bool, isMatrix: Bool) -> NSColor {
if !isCurrentLine { return .clear }
let absBg: NSColor = NSApplication.isDarkMode ? .black : .white
switch thePool.layout {
case .horizontal where isMatrix:
return NSApplication.isDarkMode ? .controlTextColor.withAlphaComponent(0.05) : .white
case .vertical where isMatrix:
return absBg.withAlphaComponent(0.9)
default:
return .clear
}
}
private var finalContainerOrientation: NSUserInterfaceLayoutOrientation {
if thePool.maxLinesPerPage == 1, thePool.layout == .horizontal { return .horizontal }
return .vertical
}
}
// MARK: - Debug Module Using Swift UI.
import SwiftUI
@available(macOS 10.15, *)
public struct VwrCandidateTDKAppKitForSwiftUI: NSViewRepresentable {
public weak var controller: CtlCandidateTDK?
public var thePool: CandidatePool
public func makeNSView(context _: Context) -> VwrCandidateTDKAppKit {
let nsView = VwrCandidateTDKAppKit(thePool: thePool)
nsView.controller = controller
return nsView
}
public func updateNSView(_ nsView: VwrCandidateTDKAppKit, context _: Context) {
nsView.thePool = thePool
}
}

View File

@ -6,11 +6,13 @@
// marks, or product names of Contributor, except as required to fulfill notice // marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License. // requirements defined in MIT License.
import AppKit #if USING_STACK_VIEW_IN_TDK_COCOA
import Shared
/// Cocoa SwiftUI import AppKit
public class VwrCandidateTDKCocoa: NSStackView { import Shared
/// Cocoa SwiftUI
public class VwrCandidateTDKCocoa: NSStackView {
public weak var controller: CtlCandidateTDK? public weak var controller: CtlCandidateTDK?
public var thePool: CandidatePool public var thePool: CandidatePool
private var lineDimension: CGSize = .zero private var lineDimension: CGSize = .zero
@ -29,11 +31,11 @@ public class VwrCandidateTDKCocoa: NSStackView {
required init?(coder _: NSCoder) { required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
} }
// MARK: - Interface Renderer. // MARK: - Interface Renderer.
public extension VwrCandidateTDKCocoa { public extension VwrCandidateTDKCocoa {
func refresh() { func refresh() {
defer { defer {
vCLog(Self.strForConstraintStatistics.description) vCLog(Self.strForConstraintStatistics.description)
@ -157,11 +159,11 @@ public extension VwrCandidateTDKCocoa {
subviews.forEach { removeView($0) } subviews.forEach { removeView($0) }
addView(finalContainer, in: .top) addView(finalContainer, in: .top)
} }
} }
// MARK: - Interface Components. // MARK: - Interface Components.
private extension VwrCandidateTDKCocoa { private extension VwrCandidateTDKCocoa {
private var candidateListBackground: NSColor { private var candidateListBackground: NSColor {
let delta = NSApplication.isDarkMode ? 0.05 : 0.99 let delta = NSApplication.isDarkMode ? 0.05 : 0.99
return .init(white: delta, alpha: 1) return .init(white: delta, alpha: 1)
@ -206,7 +208,7 @@ private extension VwrCandidateTDKCocoa {
case .horizontal where isMatrix: case .horizontal where isMatrix:
return NSApplication.isDarkMode ? .controlTextColor.withAlphaComponent(0.05) : .white return NSApplication.isDarkMode ? .controlTextColor.withAlphaComponent(0.05) : .white
case .vertical where isMatrix: case .vertical where isMatrix:
return absBg.withAlphaComponent(0.13) return absBg.withAlphaComponent(0.9)
default: default:
return .clear return .clear
} }
@ -260,11 +262,11 @@ private extension VwrCandidateTDKCocoa {
textField.sizeToFit() textField.sizeToFit()
return textField return textField
} }
} }
// MARK: - Constraint Utilities // MARK: - Constraint Utilities
private extension VwrCandidateTDKCocoa { private extension VwrCandidateTDKCocoa {
static var strForConstraintStatistics = NSMutableString(string: "TDKCandidates Dimensions (Debug):\n") static var strForConstraintStatistics = NSMutableString(string: "TDKCandidates Dimensions (Debug):\n")
static func addStatistics(_ target: NSView, memo: String = "") { static func addStatistics(_ target: NSView, memo: String = "") {
@ -282,11 +284,11 @@ private extension VwrCandidateTDKCocoa {
) )
item.addConstraint(widthConstraint) item.addConstraint(widthConstraint)
} }
} }
// MARK: - Candidate Cell View // MARK: - Candidate Cell View
private extension VwrCandidateTDKCocoa { private extension VwrCandidateTDKCocoa {
class VwrCandidateCell: NSTextField { class VwrCandidateCell: NSTextField {
public var cellData: CandidateCellData public var cellData: CandidateCellData
public init(cell: CandidateCellData) { public init(cell: CandidateCellData) {
@ -381,11 +383,11 @@ private extension VwrCandidateTDKCocoa {
target.didRightClickCandidateAt(cellData.index, action: .toFilter) target.didRightClickCandidateAt(cellData.index, action: .toFilter)
} }
} }
} }
// MARK: - Delegate Methods // MARK: - Delegate Methods
private extension VwrCandidateTDKCocoa { private extension VwrCandidateTDKCocoa {
func didSelectCandidateAt(_ pos: Int) { func didSelectCandidateAt(_ pos: Int) {
controller?.delegate?.candidatePairSelectionConfirmed(at: pos) controller?.delegate?.candidatePairSelectionConfirmed(at: pos)
} }
@ -393,14 +395,14 @@ private extension VwrCandidateTDKCocoa {
func didRightClickCandidateAt(_ pos: Int, action: CandidateContextMenuAction) { func didRightClickCandidateAt(_ pos: Int, action: CandidateContextMenuAction) {
controller?.delegate?.candidatePairRightClicked(at: pos, action: action) controller?.delegate?.candidatePairRightClicked(at: pos, action: action)
} }
} }
// MARK: - Debug Module Using Swift UI. // MARK: - Debug Module Using Swift UI.
import SwiftUI import SwiftUI
@available(macOS 10.15, *) @available(macOS 10.15, *)
public struct VwrCandidateTDKCocoaForSwiftUI: NSViewRepresentable { public struct VwrCandidateTDKCocoaForSwiftUI: NSViewRepresentable {
public weak var controller: CtlCandidateTDK? public weak var controller: CtlCandidateTDK?
public var thePool: CandidatePool public var thePool: CandidatePool
@ -414,4 +416,6 @@ public struct VwrCandidateTDKCocoaForSwiftUI: NSViewRepresentable {
nsView.thePool = thePool nsView.thePool = thePool
nsView.refresh() nsView.refresh()
} }
} }
#endif

View File

@ -180,7 +180,7 @@ extension VwrCandidateTDK {
case .horizontal where isCurrentLineInMatrix: case .horizontal where isCurrentLineInMatrix:
return colorScheme == .dark ? Color.primary.opacity(0.05) : .white return colorScheme == .dark ? Color.primary.opacity(0.05) : .white
case .vertical where isCurrentLineInMatrix: case .vertical where isCurrentLineInMatrix:
return absoluteBackgroundColor.opacity(0.13) return absoluteBackgroundColor.opacity(0.9)
default: default:
return Color.clear return Color.clear
} }
@ -308,9 +308,9 @@ extension VwrCandidateTDK {
var absoluteBackgroundColor: Color { var absoluteBackgroundColor: Color {
if colorScheme == .dark { if colorScheme == .dark {
return Color(white: 0) return Color.black
} else { } else {
return Color(white: 1) return Color.white
} }
} }
@ -455,6 +455,32 @@ struct VwrCandidateTDK_Previews: PreviewProvider {
} }
} }
} }
VStack {
HStack(alignment: .top) {
Text("田所選字窗 CG 模式").bold().font(Font.system(.title))
VStack {
VwrCandidateTDKAppKitForSwiftUI(controller: nil, thePool: thePoolX).fixedSize()
VwrCandidateTDKAppKitForSwiftUI(controller: nil, thePool: thePoolXS).fixedSize()
HStack {
VwrCandidateTDKAppKitForSwiftUI(controller: nil, thePool: thePoolY).fixedSize()
VwrCandidateTDKAppKitForSwiftUI(controller: nil, thePool: thePoolYS).fixedSize()
}
}
}
Divider()
HStack(alignment: .top) {
Text("田所選字窗 SwiftUI 模式").bold().font(Font.system(.title))
VStack {
VwrCandidateTDK(controller: nil, thePool: thePoolX, forceCatalinaCompatibility: oldOS).fixedSize()
VwrCandidateTDK(controller: nil, thePool: thePoolXS, forceCatalinaCompatibility: oldOS).fixedSize()
HStack {
VwrCandidateTDK(controller: nil, thePool: thePoolY, forceCatalinaCompatibility: oldOS).fixedSize()
VwrCandidateTDK(controller: nil, thePool: thePoolYS, forceCatalinaCompatibility: oldOS).fixedSize()
}
}
}
}
#if USING_STACK_VIEW_IN_TDK_COCOA
VStack { VStack {
HStack(alignment: .top) { HStack(alignment: .top) {
Text("田所選字窗 Cocoa 模式").bold().font(Font.system(.title)) Text("田所選字窗 Cocoa 模式").bold().font(Font.system(.title))
@ -480,5 +506,6 @@ struct VwrCandidateTDK_Previews: PreviewProvider {
} }
} }
} }
#endif
} }
} }