@@ -86,22 +86,56 @@ class CellOverlayBase: NSObject {
8686 onRemove ? ( )
8787 }
8888
89+ static let maximumOverlayHeight : CGFloat = 120
90+
91+ /// A single-line value gets exactly the cell it is editing, which is what keeps the
92+ /// glyphs from moving when the overlay opens. Only a value that actually breaks into
93+ /// lines grows, and its height budget comes from the same geometry the text view is
94+ /// configured with: `textContainerInset` is symmetric, so the content pays the top
95+ /// inset twice.
8996 static func overlayFrame( for cellFrame: NSRect , value: String ) -> NSRect {
90- let lineHeight = ThemeEngine . shared. dataGridFonts. regular. boundingRectForFont. height + 4
91- var newlineCount = 0
92- for scalar in value. unicodeScalars where scalar == " \n " {
93- newlineCount += 1
94- }
95- let lineCount = CGFloat ( newlineCount + 1 )
96- let contentHeight = max ( lineCount * lineHeight + 8 , cellFrame. height)
97- let height = min ( max ( contentHeight, cellFrame. height) , 120 )
97+ let breaks = lineBreakCount ( in: value)
98+ guard breaks > 0 else { return cellFrame }
99+
100+ let font = ThemeEngine . shared. valueFont
101+ let inset = DataGridCellTextGeometry . textContainerTopInset (
102+ rowHeight: cellFrame. height, font: font
103+ )
104+ let lineCount = CGFloat ( breaks + 1 )
105+ let contentHeight = lineCount * DataGridCellTextGeometry. lineHeight ( for: font) + 2 * inset
106+ let height = min ( max ( contentHeight, cellFrame. height) , maximumOverlayHeight)
98107 return NSRect ( x: cellFrame. origin. x, y: cellFrame. origin. y, width: cellFrame. width, height: height)
99108 }
100109
110+ /// Counts the breaks TextKit lays out, not just LF: a lone CR, NEL, or a Unicode line or
111+ /// paragraph separator each start a new line fragment, and CRLF is one break. Counting
112+ /// only "\n" classified a "line1\rline2" value as single-line, which sized the overlay
113+ /// to one row and hid the second line behind it.
114+ static func lineBreakCount( in value: String ) -> Int {
115+ var count = 0
116+ var previousWasCarriageReturn = false
117+ for scalar in value. unicodeScalars {
118+ switch scalar. value {
119+ case 0x0A :
120+ if !previousWasCarriageReturn { count += 1 }
121+ previousWasCarriageReturn = false
122+ case 0x0D :
123+ count += 1
124+ previousWasCarriageReturn = true
125+ case 0x85 , 0x2028 , 0x2029 :
126+ count += 1
127+ previousWasCarriageReturn = false
128+ default :
129+ previousWasCarriageReturn = false
130+ }
131+ }
132+ return count
133+ }
134+
101135 static func makeContainer( frame: NSRect ) -> CellOverlayContainerView {
102136 let container = CellOverlayContainerView ( frame: frame)
103137 container. wantsLayer = true
104- container. layer? . borderWidth = 2
138+ container. layer? . borderWidth = 1
105139 container. layer? . cornerRadius = 2
106140 container. layer? . masksToBounds = true
107141 container. applyLayerColors ( )
@@ -130,18 +164,32 @@ class CellOverlayBase: NSObject {
130164 textView. textContainer? . containerSize = unbounded
131165 }
132166
133- static func makeScrollView( in container: NSView ) -> NSScrollView {
167+ /// A row-height overlay holding a font taller than the row would otherwise show a
168+ /// vertical scroller and scroll its own descenders; a single-line value has nothing to
169+ /// scroll to, so the vertical axis is shut off entirely.
170+ static func makeScrollView( in container: NSView , scrollsVertically: Bool ) -> NSScrollView {
134171 let scrollView = NSScrollView ( frame: container. bounds)
135172 scrollView. autoresizingMask = [ . width, . height]
136- scrollView. hasVerticalScroller = true
173+ scrollView. hasVerticalScroller = scrollsVertically
137174 scrollView. hasHorizontalScroller = false
138175 scrollView. autohidesScrollers = true
139176 scrollView. borderType = . noBorder
140177 scrollView. drawsBackground = true
141178 scrollView. backgroundColor = . textBackgroundColor
179+ if !scrollsVertically {
180+ scrollView. verticalScrollElasticity = . none
181+ }
142182 return scrollView
143183 }
144184
185+ static func configureCellTextGeometry( of textView: NSTextView , rowHeight: CGFloat , font: NSFont ) {
186+ textView. textContainer? . lineFragmentPadding = DataGridMetrics . cellHorizontalInset
187+ textView. textContainerInset = NSSize (
188+ width: 0 ,
189+ height: DataGridCellTextGeometry . textContainerTopInset ( rowHeight: rowHeight, font: font)
190+ )
191+ }
192+
145193 private func installDismissObservers( ) {
146194 guard let hostTableView else { return }
147195
0 commit comments