Skip to content

Commit 1db9eac

Browse files
committed
Center vertical candidate window text
1 parent 73d0379 commit 1db9eac

2 files changed

Lines changed: 107 additions & 17 deletions

File tree

Packages/CandidateUI/Sources/CandidateUI/VerticalCandidateController.swift

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,16 @@ import Cocoa
2525

2626
private class VerticalKeyLabelStripView: NSView {
2727
var keyLabelFont: NSFont = .systemFont(ofSize: NSFont.smallSystemFontSize)
28-
var labelOffsetY: CGFloat = 0
2928
var keyLabels: [String] = []
3029
var highlightedIndex: Int = -1
30+
private let textCell: VerticallyCenteredTextFieldCell = {
31+
let cell = VerticallyCenteredTextFieldCell()
32+
cell.isBezeled = false
33+
cell.isBordered = false
34+
cell.drawsBackground = false
35+
cell.isEditable = false
36+
return cell
37+
}()
3138

3239
override var isFlipped: Bool {
3340
true
@@ -76,9 +83,6 @@ private class VerticalKeyLabelStripView: NSView {
7683
.paragraphStyle: paraStyle,
7784
]
7885
for index in 0..<count {
79-
let textRect = NSRect(
80-
x: 0.0, y: CGFloat(index) * cellHeight + labelOffsetY, width: bounds.size.width,
81-
height: cellHeight - labelOffsetY)
8286
var cellRect = NSRect(
8387
x: 0.0, y: CGFloat(index) * cellHeight, width: bounds.size.width, height: cellHeight
8488
)
@@ -92,14 +96,16 @@ private class VerticalKeyLabelStripView: NSView {
9296
NSColor.selectedControlColor.setFill()
9397
NSBezierPath.fill(cellRect)
9498
}
95-
(text as NSString).draw(
96-
in: textRect,
97-
withAttributes: (index == highlightedIndex) ? textAttrHighlighted : textAttr)
99+
textCell.attributedStringValue = NSAttributedString(
100+
string: text,
101+
attributes: (index == highlightedIndex) ? textAttrHighlighted : textAttr)
98102
} else {
99103
(index == highlightedIndex ? darkGray : lightGray).setFill()
100104
NSBezierPath.fill(cellRect)
101-
(text as NSString).draw(in: textRect, withAttributes: textAttr)
105+
textCell.attributedStringValue = NSAttributedString(
106+
string: text, attributes: textAttr)
102107
}
108+
textCell.draw(withFrame: cellRect, in: self)
103109
}
104110
}
105111
}
@@ -109,6 +115,28 @@ private let kCandidateTextLeftMargin: CGFloat = 8.0
109115
private let kCandidateTextPaddingWithMandatedTableViewPadding: CGFloat = 18.0
110116
private let kCandidateTextLeftMarginWithMandatedTableViewPadding: CGFloat = 0.0
111117

118+
final class VerticallyCenteredTextFieldCell: NSTextFieldCell {
119+
override func drawingRect(forBounds rect: NSRect) -> NSRect {
120+
let drawingRect = super.drawingRect(forBounds: rect)
121+
let textHeight = min(drawingRect.height, cellSize(forBounds: drawingRect).height)
122+
return NSRect(
123+
x: drawingRect.minX,
124+
y: drawingRect.minY + (drawingRect.height - textHeight) / 2,
125+
width: drawingRect.width,
126+
height: textHeight)
127+
}
128+
}
129+
130+
func tooltipFrame(
131+
windowWidth: CGFloat, windowHeight: CGFloat, tooltipHeight: CGFloat, padding: CGFloat
132+
) -> NSRect {
133+
NSRect(
134+
x: padding,
135+
y: windowHeight - tooltipHeight,
136+
width: max(0, windowWidth - padding * 2),
137+
height: tooltipHeight)
138+
}
139+
112140
// Only used in macOS 10.15 (Catalina) or lower
113141
private class BackgroundView: NSView {
114142
override func draw(_: NSRect) {
@@ -264,6 +292,7 @@ public class VerticalCandidateController: CandidateController {
264292
}
265293

266294
tooltipView = NSTextField(frame: NSRect.zero)
295+
tooltipView.cell = VerticallyCenteredTextFieldCell()
267296
tooltipView.isEditable = false
268297
tooltipView.isSelectable = false
269298
tooltipView.isBezeled = false
@@ -290,7 +319,7 @@ public class VerticalCandidateController: CandidateController {
290319

291320
tableView = VerticalCandidateTableView(frame: contentRect)
292321
let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "candidate"))
293-
column.dataCell = NSTextFieldCell()
322+
column.dataCell = VerticallyCenteredTextFieldCell()
294323
column.isEditable = false
295324

296325
candidateTextPadding = kCandidateTextPadding
@@ -629,8 +658,8 @@ extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegat
629658
if !tooltip.isEmpty {
630659
tooltipView.stringValue = tooltip
631660
let size = tooltipView.intrinsicContentSize
632-
tooltipWidth = size.width + tooltipPadding * 2
633-
tooltipHeight = size.height + tooltipPadding * 2
661+
tooltipWidth = ceil(size.width) + tooltipPadding * 2
662+
tooltipHeight = ceil(size.height) + tooltipPadding * 2
634663
window?.contentView?.addSubview(tooltipView)
635664
} else {
636665
tooltipView.removeFromSuperview()
@@ -654,9 +683,6 @@ extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegat
654683
keyLabelStripView.keyLabelFont = keyLabelFont
655684
let actualKeyLabels = keyLabels[0..<Int(keyLabelCount)].map { $0.displayedText }
656685
keyLabelStripView.keyLabels = actualKeyLabels
657-
keyLabelStripView.labelOffsetY =
658-
(keyLabelFontSize >= candidateFontSize)
659-
? 0.0 : floor((candidateFontSize - keyLabelFontSize) / 2.0)
660686

661687
let rowHeight = ceil(fontSize * 1.25)
662688
tableView.rowHeight = rowHeight
@@ -689,9 +715,9 @@ extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegat
689715
scrollView.frame = NSRect(
690716
x: stripWidth + 1.0, y: 0, width: windowWidth - stripWidth - 1,
691717
height: windowHeight - tooltipHeight)
692-
tooltipView.frame = NSRect(
693-
x: tooltipPadding, y: windowHeight - tooltipHeight + tooltipPadding, width: windowWidth,
694-
height: tooltipHeight)
718+
tooltipView.frame = tooltipFrame(
719+
windowWidth: windowWidth, windowHeight: windowHeight, tooltipHeight: tooltipHeight,
720+
padding: tooltipPadding)
695721
window?.setFrame(frameRect, display: false)
696722
}
697723
}

Packages/CandidateUI/Tests/CandidateUITests/VerticalCandidateControllerTests.swift

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,4 +190,68 @@ final class VerticalCandidateControllerTests {
190190
#expect(controller.selectedCandidateIndex == 0)
191191
}
192192

193+
@Test("Test if candidate text is vertically centered in its row")
194+
func testCandidateTextVerticalAlignment() {
195+
let fontSize: CGFloat = 16
196+
let cell = VerticallyCenteredTextFieldCell()
197+
cell.attributedStringValue = NSAttributedString(
198+
string: "",
199+
attributes: [.font: NSFont.systemFont(ofSize: fontSize)])
200+
let bounds = NSRect(x: 0, y: 0, width: 100, height: ceil(fontSize * 1.25))
201+
202+
let drawingRect = cell.drawingRect(forBounds: bounds)
203+
204+
#expect(drawingRect.height < bounds.height)
205+
#expect(abs(drawingRect.midY - bounds.midY) < 0.001)
206+
}
207+
208+
@Test("Test if key labels are vertically centered in their rows")
209+
func testKeyLabelVerticalAlignment() {
210+
let cell = VerticallyCenteredTextFieldCell()
211+
cell.attributedStringValue = NSAttributedString(
212+
string: "1",
213+
attributes: [.font: NSFont.systemFont(ofSize: 10)])
214+
let bounds = NSRect(x: 0, y: 0, width: 25, height: 21)
215+
216+
let drawingRect = cell.drawingRect(forBounds: bounds)
217+
218+
#expect(drawingRect.height < bounds.height)
219+
#expect(abs(drawingRect.midY - bounds.midY) < 0.001)
220+
}
221+
222+
@Test("Test if the tooltip is centered and inset within the window")
223+
func testTooltipAlignment() {
224+
let cell = VerticallyCenteredTextFieldCell()
225+
cell.attributedStringValue = NSAttributedString(
226+
string: "將進酒",
227+
attributes: [.font: NSFont.systemFont(ofSize: 13)])
228+
let padding: CGFloat = 2
229+
let contentHeight = ceil(cell.cellSize.height) + padding * 2
230+
let bounds = NSRect(x: 0, y: 0, width: 100, height: contentHeight)
231+
232+
let drawingRect = cell.drawingRect(forBounds: bounds)
233+
let frame = tooltipFrame(
234+
windowWidth: 100, windowHeight: 200, tooltipHeight: contentHeight, padding: padding)
235+
236+
#expect(abs(drawingRect.midY - bounds.midY) < 0.001)
237+
#expect(frame.minX == padding)
238+
#expect(frame.maxX == 100 - padding)
239+
#expect(frame.maxY == 200)
240+
}
241+
242+
@Test("Test if oversized candidate text stays within its row")
243+
func testOversizedCandidateTextVerticalAlignment() {
244+
let cell = VerticallyCenteredTextFieldCell()
245+
cell.attributedStringValue = NSAttributedString(
246+
string: "",
247+
attributes: [.font: NSFont.systemFont(ofSize: 32)])
248+
let bounds = NSRect(x: 0, y: 0, width: 100, height: 20)
249+
250+
let drawingRect = cell.drawingRect(forBounds: bounds)
251+
252+
#expect(drawingRect.minY >= bounds.minY)
253+
#expect(drawingRect.maxY <= bounds.maxY)
254+
#expect(drawingRect.height == bounds.height)
255+
}
256+
193257
}

0 commit comments

Comments
 (0)