fix: center emoji artwork in launcher icon slot

This commit is contained in:
Valdemar Lorenzen
2026-10-15 15:35:20 +02:00
parent da1493a3c8
commit 0ba61153d4
3 changed files with 37 additions and 4 deletions
+1 -1
View File
@@ -81,7 +81,7 @@ Currency and crypto conversions require internet access and use [currency-api](h
## Emoji picker ## Emoji picker
Search for **Conjure Emoji** and open it. Browse the grid or search by name, keyword, `:shortcode:`, or emoji. Use the category menu to filter results or show **Recently Used**. Your last 30 selections are saved locally. Favorites appear first, followed by recent selections when browsing All Emoji. Search for **Conjure Emoji**, identified by its centered 🤔 icon, and open it. Browse the grid or search by name, keyword, `:shortcode:`, or emoji. Use the category menu to filter results or show **Recently Used**. Your last 30 selections are saved locally. Favorites appear first, followed by recent selections when browsing All Emoji.
Use the arrow keys or Control-H/J/K/L to navigate left/down/up/right. Return or clicking an emoji copies it and dismisses Conjure, restoring focus to the previous app. Paste with Command-V; no Accessibility permission is needed. Escape or the Back chevron returns to the launcher and restores your search. Use the arrow keys or Control-H/J/K/L to navigate left/down/up/right. Return or clicking an emoji copies it and dismisses Conjure, restoring focus to the previous app. Paste with Command-V; no Accessibility permission is needed. Escape or the Back chevron returns to the launcher and restores your search.
@@ -1,5 +1,6 @@
import AppKit import AppKit
import ConjureKit import ConjureKit
import CoreText
@MainActor @MainActor
final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSearchFieldDelegate, NSTableViewDataSource, NSTableViewDelegate { final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSearchFieldDelegate, NSTableViewDataSource, NSTableViewDelegate {
@@ -40,9 +41,13 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
private var pendingPresentation: (() -> Void)? private var pendingPresentation: (() -> Void)?
private static let emojiIcon: NSImage = { private static let emojiIcon: NSImage = {
let text = NSAttributedString(string: "🤔", attributes: [.font: NSFont.systemFont(ofSize: 28)]) let text = NSAttributedString(string: "🤔", attributes: [.font: NSFont.systemFont(ofSize: 28)])
let size = text.size() let line = CTLineCreateWithAttributedString(text)
return NSImage(size: size, flipped: false) { _ in return NSImage(size: NSSize(width: 32, height: 32), flipped: false) { rect in
text.draw(at: .zero) guard let context = NSGraphicsContext.current?.cgContext else { return false }
context.textPosition = .zero
let bounds = CTLineGetImageBounds(line, context)
context.textPosition = CGPoint(x: rect.midX - bounds.midX, y: rect.midY - bounds.midY)
CTLineDraw(line, context)
return true return true
} }
}() }()
@@ -0,0 +1,28 @@
import AppKit
import Testing
@testable import Conjure
@Test @MainActor func emojiCommandArtworkHasBalancedVisiblePadding() throws {
_ = NSApplication.shared
let launcher = LauncherWindowController()
func descendants(_ view: NSView) -> [NSView] { [view] + view.subviews.flatMap(descendants) }
let views = descendants(try #require(launcher.window?.contentView))
let search = try #require(views.compactMap { $0 as? NSSearchField }.first)
search.stringValue = "emoji"
launcher.controlTextDidChange(Notification(name: NSControl.textDidChangeNotification, object: search))
let table = try #require(views.compactMap { $0 as? NSTableView }.first)
let cell = try #require(launcher.tableView(table, viewFor: nil, row: 0) as? ApplicationCell)
let image = try #require(cell.imageView?.image)
let data = try #require(image.tiffRepresentation)
let bitmap = try #require(NSBitmapImageRep(data: data))
var left = bitmap.pixelsWide, right = -1, top = bitmap.pixelsHigh, bottom = -1
for y in 0..<bitmap.pixelsHigh {
for x in 0..<bitmap.pixelsWide where (bitmap.colorAt(x: x, y: y)?.alphaComponent ?? 0) > 0.5 {
left = min(left, x); right = max(right, x)
top = min(top, y); bottom = max(bottom, y)
}
}
#expect(right > left && bottom > top)
#expect(abs(left - (bitmap.pixelsWide - right - 1)) <= 2)
#expect(abs(top - (bitmap.pixelsHigh - bottom - 1)) <= 2)
}