From ab4ae41af62108b6ed12032572072bfc2a1d4218 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 28 Feb 2023 16:58:13 +0800 Subject: [PATCH] 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. --- .../CandidateCellData_Core.swift | 1 + .../TDKCandidates/CtlCandidateTDK.swift | 1 + .../TDKCandidates/VwrCandidateTDK_Cocoa.swift | 107 +++++++++++++++--- 3 files changed, 96 insertions(+), 13 deletions(-) diff --git a/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/CandidateCellData_Core.swift b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/CandidateCellData_Core.swift index b1bf153c..10981c5d 100644 --- a/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/CandidateCellData_Core.swift +++ b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/CandidateCellData_Core.swift @@ -15,6 +15,7 @@ import SwiftUIBackports /// 用來管理選字窗內顯示的候選字的單位。用 class 型別會比較方便一些。 public class CandidateCellData: Hashable { + public var visualDimension: CGSize = .zero public var locale = "" public static var unifiedSize: Double = 16 public var key: String diff --git a/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/TDKCandidates/CtlCandidateTDK.swift b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/TDKCandidates/CtlCandidateTDK.swift index 5eb476da..d993f447 100644 --- a/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/TDKCandidates/CtlCandidateTDK.swift +++ b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/TDKCandidates/CtlCandidateTDK.swift @@ -89,6 +89,7 @@ public class CtlCandidateTDK: CtlCandidate, NSWindowDelegate { Self.thePool.reverseLookupResult = reverseLookupResult } DispatchQueue.main.async { [self] in + window.isOpaque = false window.backgroundColor = .clear viewCheck: if #available(macOS 10.15, *) { if useCocoa { diff --git a/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/TDKCandidates/VwrCandidateTDK_Cocoa.swift b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/TDKCandidates/VwrCandidateTDK_Cocoa.swift index 540dac92..ceace224 100644 --- a/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/TDKCandidates/VwrCandidateTDK_Cocoa.swift +++ b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/TDKCandidates/VwrCandidateTDK_Cocoa.swift @@ -13,13 +13,15 @@ import Shared public class VwrCandidateTDKCocoa: NSStackView { public weak var controller: CtlCandidateTDK? public var thePool: CandidatePool + private var lineDimension: CGSize = .zero + private var candidateAreaDimension: CGSize = .zero // MARK: - Constructors. public init(controller: CtlCandidateTDK? = nil, thePool pool: CandidatePool) { self.controller = controller 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() } @@ -33,6 +35,15 @@ public class VwrCandidateTDKCocoa: NSStackView { public extension VwrCandidateTDKCocoa { 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) wantsLayer = true @@ -51,6 +62,7 @@ public extension VwrCandidateTDKCocoa { var theLine = thePool.candidateLines[lineID] let vwrCurrentLine = generateLineContainer(&theLine) candidateContainer.addView(vwrCurrentLine, in: isVerticalListing ? .top : .leading) + arrStackViewsOfLines.append(vwrCurrentLine) } if thePool.maxLinesPerPage - thePool.lineRangeForCurrentPage.count > 0 { thePool.lineRangeForFinalPageBlanked.enumerated().forEach { _ in @@ -60,26 +72,54 @@ public extension VwrCandidateTDKCocoa { } let vwrCurrentLine = generateLineContainer(&theLine) candidateContainer.addView(vwrCurrentLine, in: isVerticalListing ? .top : .leading) + arrStackViewsOfLines.append(vwrCurrentLine) } } // 處理行寬或列高。 switch thePool.layout { case .vertical: - let minHeight = Double(thePool.maxLineCapacity) - * drawCellCocoa(thePool.blankCell).fittingSize.height - candidateContainer.subviews.forEach { vwrCurrentLine in - Self.makeSimpleConstraint(item: vwrCurrentLine, attribute: .height, relation: .equal, value: minHeight) + var accumulatedWidth: CGFloat = 0 + var lines = [[CandidateCellData]]() + thePool.lineRangeForCurrentPage.forEach { lines.append(thePool.candidateLines[$0]) } + 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: - let containerWidth = candidateContainer.fittingSize.width - candidateContainer.subviews.forEach { vwrCurrentLine in - Self.makeSimpleConstraint(item: vwrCurrentLine, attribute: .width, relation: .equal, value: containerWidth) + candidateAreaDimension.width = accumulatedWidth + candidateAreaDimension.height = lineDimension.height + case .horizontal: + 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) + 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() @@ -94,12 +134,23 @@ public extension VwrCandidateTDKCocoa { finalContainer.spacing = 5 } else { finalContainer.spacing = 2 + Self.makeSimpleConstraint(item: vwrPeripherals, attribute: .width, relation: .greaterThanOrEqual, value: vwrPeripherals.fittingSize.width) } + Self.addStatistics(vwrPeripherals, memo: "vwrPeripherals") finalContainer.orientation = finalContainerOrientation finalContainer.alignment = finalContainerOrientation == .vertical ? .leading : .centerY finalContainer.addView(candidateContainer, 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) } @@ -119,6 +170,9 @@ private extension VwrCandidateTDKCocoa { let theCell = theCell ?? thePool.blankCell let cellLabel = VwrCandidateCell(cell: theCell) 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 padding: CGFloat = 3 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?.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 wrappedCell.setHuggingPriority(.fittingSizeCompression, for: .horizontal) wrappedCell.setHuggingPriority(.fittingSizeCompression, for: .vertical) - wrappedCell.translatesAutoresizingMaskIntoConstraints = false Self.makeSimpleConstraint(item: wrappedCell, attribute: .height, relation: .equal, value: cellHeight) switch thePool.layout { case .horizontal where thePool.maxLinesPerPage > 1: @@ -140,6 +193,8 @@ private extension VwrCandidateTDKCocoa { default: Self.makeSimpleConstraint(item: wrappedCell, attribute: .width, relation: .greaterThanOrEqual, value: cellWidth) } + Self.addStatistics(wrappedCell, memo: "wrappedCell") + theCell.visualDimension = .init(width: cellWidth, height: cellHeight) return wrappedCell } @@ -162,9 +217,20 @@ private extension VwrCandidateTDKCocoa { let vwrCurrentLine = NSStackView() vwrCurrentLine.spacing = 0 vwrCurrentLine.orientation = isVerticalListing ? .vertical : .horizontal + var cellHeight = 0.0 + var lineSize: CGSize = .zero let isCurrentLine = theLine.hasHighlightedCell theLine.forEach { theCell in 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) vwrCurrentLine.wantsLayer = isCurrentLine && isMatrix @@ -172,6 +238,11 @@ private extension VwrCandidateTDKCocoa { vwrCurrentLine.layer?.backgroundColor = lineBg.cgColor 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 } @@ -192,7 +263,17 @@ private extension VwrCandidateTDKCocoa { // MARK: - Constraint Utilities 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) { + item.translatesAutoresizingMaskIntoConstraints = false let widthConstraint = NSLayoutConstraint( item: item, attribute: attribute, relatedBy: relation, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: value @@ -208,7 +289,7 @@ private extension VwrCandidateTDKCocoa { public var cellData: CandidateCellData public init(cell: CandidateCellData) { 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 isEditable = false isBordered = false