TDKCandidates // Ensure compatibility with macOS 10.9 Mavericks.

* Refine constraint calculation methods.
* Reintroduce `window.isOpaque = false`.
* Fix translatesAutoresizingMaskIntoConstraints().
* Debugging dimensions of UI components.
* Avoid using zero rects on construction.
This commit is contained in:
ShikiSuen 2023-02-28 16:58:13 +08:00
parent cf04e09acd
commit ab4ae41af6
3 changed files with 96 additions and 13 deletions

View File

@ -15,6 +15,7 @@ import SwiftUIBackports
/// class 便 /// class 便
public class CandidateCellData: Hashable { public class CandidateCellData: Hashable {
public var visualDimension: CGSize = .zero
public var locale = "" public var locale = ""
public static var unifiedSize: Double = 16 public static var unifiedSize: Double = 16
public var key: String public var key: String

View File

@ -89,6 +89,7 @@ public class CtlCandidateTDK: CtlCandidate, NSWindowDelegate {
Self.thePool.reverseLookupResult = reverseLookupResult Self.thePool.reverseLookupResult = reverseLookupResult
} }
DispatchQueue.main.async { [self] in DispatchQueue.main.async { [self] in
window.isOpaque = false
window.backgroundColor = .clear window.backgroundColor = .clear
viewCheck: if #available(macOS 10.15, *) { viewCheck: if #available(macOS 10.15, *) {
if useCocoa { if useCocoa {

View File

@ -13,13 +13,15 @@ import Shared
public class VwrCandidateTDKCocoa: NSStackView { 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 candidateAreaDimension: CGSize = .zero
// MARK: - Constructors. // MARK: - Constructors.
public init(controller: CtlCandidateTDK? = nil, thePool pool: CandidatePool) { public init(controller: CtlCandidateTDK? = nil, thePool pool: CandidatePool) {
self.controller = controller self.controller = controller
thePool = pool thePool = pool
super.init(frame: .init(origin: .zero, size: .zero)) super.init(frame: .init(origin: .zero, size: .init(width: 114_514, height: 114_514)))
refresh() refresh()
} }
@ -33,6 +35,15 @@ public class VwrCandidateTDKCocoa: NSStackView {
public extension VwrCandidateTDKCocoa { public extension VwrCandidateTDKCocoa {
func refresh() { func refresh() {
defer {
vCLog(Self.strForConstraintStatistics.description)
Self.strForConstraintStatistics = .init()
}
// 便 constraints
var arrStackViewsOfLines = [NSStackView]()
//
lineDimension = .zero
candidateAreaDimension = .zero
// //
edgeInsets = .init(top: 5, left: 5, bottom: 5, right: 5) edgeInsets = .init(top: 5, left: 5, bottom: 5, right: 5)
wantsLayer = true wantsLayer = true
@ -51,6 +62,7 @@ public extension VwrCandidateTDKCocoa {
var theLine = thePool.candidateLines[lineID] var theLine = thePool.candidateLines[lineID]
let vwrCurrentLine = generateLineContainer(&theLine) let vwrCurrentLine = generateLineContainer(&theLine)
candidateContainer.addView(vwrCurrentLine, in: isVerticalListing ? .top : .leading) candidateContainer.addView(vwrCurrentLine, in: isVerticalListing ? .top : .leading)
arrStackViewsOfLines.append(vwrCurrentLine)
} }
if thePool.maxLinesPerPage - thePool.lineRangeForCurrentPage.count > 0 { if thePool.maxLinesPerPage - thePool.lineRangeForCurrentPage.count > 0 {
thePool.lineRangeForFinalPageBlanked.enumerated().forEach { _ in thePool.lineRangeForFinalPageBlanked.enumerated().forEach { _ in
@ -60,26 +72,54 @@ public extension VwrCandidateTDKCocoa {
} }
let vwrCurrentLine = generateLineContainer(&theLine) let vwrCurrentLine = generateLineContainer(&theLine)
candidateContainer.addView(vwrCurrentLine, in: isVerticalListing ? .top : .leading) candidateContainer.addView(vwrCurrentLine, in: isVerticalListing ? .top : .leading)
arrStackViewsOfLines.append(vwrCurrentLine)
} }
} }
// //
switch thePool.layout { switch thePool.layout {
case .vertical: case .vertical:
let minHeight = Double(thePool.maxLineCapacity) var accumulatedWidth: CGFloat = 0
* drawCellCocoa(thePool.blankCell).fittingSize.height var lines = [[CandidateCellData]]()
candidateContainer.subviews.forEach { vwrCurrentLine in thePool.lineRangeForCurrentPage.forEach { lines.append(thePool.candidateLines[$0]) }
Self.makeSimpleConstraint(item: vwrCurrentLine, attribute: .height, relation: .equal, value: minHeight) arrStackViewsOfLines.enumerated().forEach { viewLineID, vwrCurrentLine in
var columnWidth: CGFloat = 0
if (0 ..< lines.count).contains(viewLineID), !lines.isEmpty {
let line = Array(lines[viewLineID])
columnWidth = line.map(\.visualDimension.width).max() ?? lineDimension.width
} else {
columnWidth = thePool.blankCell.visualDimension.width
}
accumulatedWidth += columnWidth
Self.makeSimpleConstraint(item: vwrCurrentLine, attribute: .width, relation: .equal, value: columnWidth)
Self.makeSimpleConstraint(item: vwrCurrentLine, attribute: .height, relation: .equal, value: lineDimension.height)
Self.addStatistics(vwrCurrentLine, memo: "vwrCurrentLine")
} }
case .horizontal where thePool.maxLinesPerPage > 1: candidateAreaDimension.width = accumulatedWidth
let containerWidth = candidateContainer.fittingSize.width candidateAreaDimension.height = lineDimension.height
candidateContainer.subviews.forEach { vwrCurrentLine in case .horizontal:
Self.makeSimpleConstraint(item: vwrCurrentLine, attribute: .width, relation: .equal, value: containerWidth) arrStackViewsOfLines.forEach { vwrCurrentLine in
Self.makeSimpleConstraint(item: vwrCurrentLine, attribute: .width, relation: .equal, value: lineDimension.width)
Self.makeSimpleConstraint(item: vwrCurrentLine, attribute: .height, relation: .equal, value: lineDimension.height)
Self.addStatistics(vwrCurrentLine, memo: "vwrCurrentLine")
} }
default: break candidateAreaDimension.width = lineDimension.width
candidateAreaDimension.height = lineDimension.height * Double(thePool.maxLinesPerPage)
} }
Self.makeSimpleConstraint(item: candidateContainer, attribute: .width, relation: .equal, value: candidateAreaDimension.width)
Self.makeSimpleConstraint(item: candidateContainer, attribute: .height, relation: .equal, value: candidateAreaDimension.height)
Self.addStatistics(candidateContainer, memo: "candidateContainer")
let vwrPeripherals = Self.makeLabel(thePool.attributedDescriptionBottomPanes) let vwrPeripherals = Self.makeLabel(thePool.attributedDescriptionBottomPanes)
Self.makeSimpleConstraint(
item: vwrPeripherals, attribute: .height, relation: .greaterThanOrEqual,
value: vwrPeripherals.fittingSize.height
)
Self.makeSimpleConstraint(
item: vwrPeripherals, attribute: .width, relation: .greaterThanOrEqual,
value: vwrPeripherals.fittingSize.width
)
// //
let finalContainer = NSStackView() let finalContainer = NSStackView()
@ -94,12 +134,23 @@ public extension VwrCandidateTDKCocoa {
finalContainer.spacing = 5 finalContainer.spacing = 5
} else { } else {
finalContainer.spacing = 2 finalContainer.spacing = 2
Self.makeSimpleConstraint(item: vwrPeripherals, attribute: .width, relation: .greaterThanOrEqual, value: vwrPeripherals.fittingSize.width)
} }
Self.addStatistics(vwrPeripherals, memo: "vwrPeripherals")
finalContainer.orientation = finalContainerOrientation finalContainer.orientation = finalContainerOrientation
finalContainer.alignment = finalContainerOrientation == .vertical ? .leading : .centerY finalContainer.alignment = finalContainerOrientation == .vertical ? .leading : .centerY
finalContainer.addView(candidateContainer, in: .leading) finalContainer.addView(candidateContainer, in: .leading)
finalContainer.addView(vwrPeripherals, in: .leading) finalContainer.addView(vwrPeripherals, in: .leading)
Self.makeSimpleConstraint(
item: finalContainer, attribute: .width,
relation: .equal, value: finalContainer.fittingSize.width
)
Self.makeSimpleConstraint(
item: finalContainer, attribute: .height,
relation: .equal, value: finalContainer.fittingSize.height
)
Self.addStatistics(finalContainer, memo: "finalContainer")
// //
subviews.forEach { removeView($0) } subviews.forEach { removeView($0) }
@ -119,6 +170,9 @@ private extension VwrCandidateTDKCocoa {
let theCell = theCell ?? thePool.blankCell let theCell = theCell ?? thePool.blankCell
let cellLabel = VwrCandidateCell(cell: theCell) let cellLabel = VwrCandidateCell(cell: theCell)
cellLabel.target = self cellLabel.target = self
Self.makeSimpleConstraint(item: cellLabel, attribute: .width, relation: .equal, value: cellLabel.fittingSize.width)
Self.makeSimpleConstraint(item: cellLabel, attribute: .height, relation: .equal, value: cellLabel.fittingSize.height)
Self.addStatistics(cellLabel, memo: "cellLabel")
let wrappedCell = NSStackView() let wrappedCell = NSStackView()
let padding: CGFloat = 3 let padding: CGFloat = 3
wrappedCell.edgeInsets = .init(top: padding, left: padding, bottom: padding, right: padding) wrappedCell.edgeInsets = .init(top: padding, left: padding, bottom: padding, right: padding)
@ -128,11 +182,10 @@ private extension VwrCandidateTDKCocoa {
wrappedCell.layer?.backgroundColor = theCell.themeColorCocoa.cgColor wrappedCell.layer?.backgroundColor = theCell.themeColorCocoa.cgColor
wrappedCell.layer?.cornerRadius = padding * 2 wrappedCell.layer?.cornerRadius = padding * 2
} }
let cellWidth = thePool.cellWidth(theCell).min ?? wrappedCell.fittingSize.width let cellWidth = max(thePool.cellWidth(theCell).min ?? wrappedCell.fittingSize.width, wrappedCell.fittingSize.width)
let cellHeight = wrappedCell.fittingSize.height let cellHeight = wrappedCell.fittingSize.height
wrappedCell.setHuggingPriority(.fittingSizeCompression, for: .horizontal) wrappedCell.setHuggingPriority(.fittingSizeCompression, for: .horizontal)
wrappedCell.setHuggingPriority(.fittingSizeCompression, for: .vertical) wrappedCell.setHuggingPriority(.fittingSizeCompression, for: .vertical)
wrappedCell.translatesAutoresizingMaskIntoConstraints = false
Self.makeSimpleConstraint(item: wrappedCell, attribute: .height, relation: .equal, value: cellHeight) Self.makeSimpleConstraint(item: wrappedCell, attribute: .height, relation: .equal, value: cellHeight)
switch thePool.layout { switch thePool.layout {
case .horizontal where thePool.maxLinesPerPage > 1: case .horizontal where thePool.maxLinesPerPage > 1:
@ -140,6 +193,8 @@ private extension VwrCandidateTDKCocoa {
default: default:
Self.makeSimpleConstraint(item: wrappedCell, attribute: .width, relation: .greaterThanOrEqual, value: cellWidth) Self.makeSimpleConstraint(item: wrappedCell, attribute: .width, relation: .greaterThanOrEqual, value: cellWidth)
} }
Self.addStatistics(wrappedCell, memo: "wrappedCell")
theCell.visualDimension = .init(width: cellWidth, height: cellHeight)
return wrappedCell return wrappedCell
} }
@ -162,9 +217,20 @@ private extension VwrCandidateTDKCocoa {
let vwrCurrentLine = NSStackView() let vwrCurrentLine = NSStackView()
vwrCurrentLine.spacing = 0 vwrCurrentLine.spacing = 0
vwrCurrentLine.orientation = isVerticalListing ? .vertical : .horizontal vwrCurrentLine.orientation = isVerticalListing ? .vertical : .horizontal
var cellHeight = 0.0
var lineSize: CGSize = .zero
let isCurrentLine = theLine.hasHighlightedCell let isCurrentLine = theLine.hasHighlightedCell
theLine.forEach { theCell in theLine.forEach { theCell in
vwrCurrentLine.addView(drawCellCocoa(theCell), in: isVerticalListing ? .top : .leading) vwrCurrentLine.addView(drawCellCocoa(theCell), in: isVerticalListing ? .top : .leading)
switch thePool.layout {
case .horizontal:
lineSize.width += theCell.visualDimension.width
lineSize.height = max(lineSize.height, theCell.visualDimension.height)
case .vertical:
lineSize.width = max(lineSize.width, theCell.visualDimension.width)
lineSize.height += theCell.visualDimension.height
}
cellHeight = max(theCell.visualDimension.height, cellHeight)
} }
let lineBg = lineBackground(isCurrentLine: isCurrentLine, isMatrix: isMatrix) let lineBg = lineBackground(isCurrentLine: isCurrentLine, isMatrix: isMatrix)
vwrCurrentLine.wantsLayer = isCurrentLine && isMatrix vwrCurrentLine.wantsLayer = isCurrentLine && isMatrix
@ -172,6 +238,11 @@ private extension VwrCandidateTDKCocoa {
vwrCurrentLine.layer?.backgroundColor = lineBg.cgColor vwrCurrentLine.layer?.backgroundColor = lineBg.cgColor
vwrCurrentLine.layer?.cornerRadius = 6 vwrCurrentLine.layer?.cornerRadius = 6
} }
lineDimension.width = max(lineSize.width, lineDimension.width)
switch thePool.layout {
case .horizontal: lineDimension.height = max(lineSize.height, lineDimension.height)
case .vertical: lineDimension.height = cellHeight * Double(thePool.maxLineCapacity)
}
return vwrCurrentLine return vwrCurrentLine
} }
@ -192,7 +263,17 @@ private extension VwrCandidateTDKCocoa {
// MARK: - Constraint Utilities // MARK: - Constraint Utilities
private extension VwrCandidateTDKCocoa { private extension VwrCandidateTDKCocoa {
static var strForConstraintStatistics = NSMutableString(string: "TDKCandidates Dimensions (Debug):\n")
static func addStatistics(_ target: NSView, memo: String = "") {
if Self.strForConstraintStatistics.length == 0 {
Self.strForConstraintStatistics.append("TDKCandidates Dimensions (Debug):\n")
}
Self.strForConstraintStatistics.append("\(target.fittingSize) \(memo)\n")
}
static func makeSimpleConstraint(item: NSView, attribute: NSLayoutConstraint.Attribute, relation: NSLayoutConstraint.Relation, value: CGFloat) { static func makeSimpleConstraint(item: NSView, attribute: NSLayoutConstraint.Attribute, relation: NSLayoutConstraint.Relation, value: CGFloat) {
item.translatesAutoresizingMaskIntoConstraints = false
let widthConstraint = NSLayoutConstraint( let widthConstraint = NSLayoutConstraint(
item: item, attribute: attribute, relatedBy: relation, toItem: nil, item: item, attribute: attribute, relatedBy: relation, toItem: nil,
attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: value attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: value
@ -208,7 +289,7 @@ private extension VwrCandidateTDKCocoa {
public var cellData: CandidateCellData public var cellData: CandidateCellData
public init(cell: CandidateCellData) { public init(cell: CandidateCellData) {
cellData = cell cellData = cell
super.init(frame: .init(origin: .zero, size: .zero)) super.init(frame: .init(origin: .zero, size: .init(width: 114_514, height: 114_514)))
isSelectable = false isSelectable = false
isEditable = false isEditable = false
isBordered = false isBordered = false