fix: keep resting content below the top fade
The 32-point viewport fade covered launcher rows with only 16 points of inset and emoji with only 12. Match their top spacing to the shared fade height while keeping the viewport mask fixed during scrolling.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import AppKit
|
||||
import ConjureKit
|
||||
import Testing
|
||||
@testable import Conjure
|
||||
|
||||
@@ -23,3 +24,47 @@ func launcherFadesStayAtViewportEdgesAfterScrollingAndResizing() throws {
|
||||
#expect(alpha(height - 1) < 20)
|
||||
}
|
||||
}
|
||||
|
||||
@Test @MainActor
|
||||
func launcherAndEmojiContentStartBelowTheTopFade() throws {
|
||||
_ = NSApplication.shared
|
||||
let suite = "ConjureFade.\(UUID().uuidString)"
|
||||
let defaults = UserDefaults(suiteName: suite)!
|
||||
defer { defaults.removePersistentDomain(forName: suite) }
|
||||
func topOpacity(_ rect: NSRect, in scroll: LauncherScrollView) throws -> UInt8 {
|
||||
let width = Int(scroll.bounds.width), height = Int(scroll.bounds.height)
|
||||
let y = Int(rect.minY - scroll.contentView.bounds.minY)
|
||||
try #require((0..<height).contains(y))
|
||||
let context = try #require(CGContext(data: nil, width: width, height: height,
|
||||
bitsPerComponent: 8, bytesPerRow: width * 4, space: CGColorSpaceCreateDeviceRGB(),
|
||||
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue))
|
||||
let mask = try #require(scroll.layer?.mask)
|
||||
mask.render(in: context)
|
||||
let pixels = try #require(context.data?.assumingMemoryBound(to: UInt8.self))
|
||||
return pixels[y * width * 4 + width / 2 * 4 + 3]
|
||||
}
|
||||
let launcher = LauncherWindowController(
|
||||
history: LaunchHistory(defaults: defaults, now: { Date(timeIntervalSinceReferenceDate: 12_000) })
|
||||
)
|
||||
let content = try #require(launcher.window?.contentView)
|
||||
let search = try #require(content.subviews.compactMap { $0 as? NSStackView }
|
||||
.flatMap(\.arrangedSubviews).compactMap { $0 as? NSSearchField }.first)
|
||||
launcher.controlTextDidChange(Notification(name: NSControl.textDidChangeNotification, object: search))
|
||||
content.layoutSubtreeIfNeeded()
|
||||
let tableScroll = try #require(content.subviews.compactMap { $0 as? LauncherScrollView }.first)
|
||||
let table = try #require(tableScroll.documentView as? NSTableView)
|
||||
let firstRow = tableScroll.contentView.convert(table.rect(ofRow: 0), from: table)
|
||||
#expect(try topOpacity(firstRow, in: tableScroll) == 255)
|
||||
|
||||
let picker = EmojiPickerController(defaults: defaults)
|
||||
let window = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 750, height: 400),
|
||||
styleMask: [.borderless], backing: .buffered, defer: false)
|
||||
window.contentView = picker.view
|
||||
picker.view.layoutSubtreeIfNeeded()
|
||||
let emojiScroll = try #require(picker.view.subviews.compactMap { $0 as? LauncherScrollView }.first)
|
||||
let grid = try #require(emojiScroll.documentView as? NSCollectionView)
|
||||
let path = IndexPath(item: 0, section: 0)
|
||||
let attributes = try #require(grid.collectionViewLayout?.layoutAttributesForItem(at: path))
|
||||
let firstEmoji = emojiScroll.contentView.convert(attributes.frame, from: grid)
|
||||
#expect(try topOpacity(firstEmoji, in: emojiScroll) == 255)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user