説明なし

IndeedLogoView.swift 5.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. //
  2. // IndeedLogoView.swift
  3. // App for Indeed
  4. //
  5. import Cocoa
  6. /// App logo from the asset catalog for splash and branding surfaces.
  7. enum IndeedBrandLogo {
  8. enum Variant {
  9. case standard
  10. case compact
  11. }
  12. static func logoImage(variant: Variant = .standard) -> NSImage? {
  13. switch variant {
  14. case .standard:
  15. NSImage(named: NSImage.Name("IndeedLogo"))
  16. case .compact:
  17. NSImage(named: NSImage.Name("IndeedLogoSmall"))
  18. }
  19. }
  20. }
  21. /// Sidebar “Indeed” row icon — outline or filled template glyph tinted like SF Symbols.
  22. enum IndeedSidebarNavIcon {
  23. private static let canvas: CGFloat = 18
  24. private static var outlineImage: NSImage?
  25. private static var filledImage: NSImage?
  26. static func image(filled: Bool) -> NSImage {
  27. if filled {
  28. if let filledImage { return filledImage }
  29. let image = makeImage(filled: true)
  30. filledImage = image
  31. return image
  32. }
  33. if let outlineImage { return outlineImage }
  34. let image = makeImage(filled: false)
  35. outlineImage = image
  36. return image
  37. }
  38. private static func makeImage(filled: Bool) -> NSImage {
  39. let size = NSSize(width: canvas, height: canvas)
  40. let image = NSImage(size: size, flipped: true) { _ in
  41. if filled {
  42. drawFilledMark()
  43. } else {
  44. drawOutlineMark()
  45. }
  46. return true
  47. }
  48. image.isTemplate = true
  49. return image
  50. }
  51. /// Stylized “i” mark (dot + stem + brow) aligned with SF Symbol nav icons.
  52. private static func drawOutlineMark() {
  53. let stroke: CGFloat = 1.25
  54. NSColor.black.setStroke()
  55. let dot = NSBezierPath(ovalIn: NSRect(x: 7.2, y: 2.8, width: 3.6, height: 3.6))
  56. dot.lineWidth = stroke
  57. dot.stroke()
  58. let stem = NSBezierPath(roundedRect: NSRect(x: 7.2, y: 7.2, width: 3.6, height: 8.2), xRadius: 1.8, yRadius: 1.8)
  59. stem.lineWidth = stroke
  60. stem.stroke()
  61. let brow = NSBezierPath()
  62. brow.move(to: NSPoint(x: 4.6, y: 6.4))
  63. brow.curve(to: NSPoint(x: 13.4, y: 6.4), controlPoint1: NSPoint(x: 6.2, y: 2.2), controlPoint2: NSPoint(x: 11.8, y: 2.2))
  64. brow.lineWidth = stroke
  65. brow.lineCapStyle = .round
  66. brow.stroke()
  67. }
  68. private static func drawFilledMark() {
  69. NSColor.black.setFill()
  70. let dot = NSBezierPath(ovalIn: NSRect(x: 7.2, y: 2.8, width: 3.6, height: 3.6))
  71. dot.fill()
  72. let stem = NSBezierPath(roundedRect: NSRect(x: 7.2, y: 7.2, width: 3.6, height: 8.2), xRadius: 1.8, yRadius: 1.8)
  73. stem.fill()
  74. let brow = NSBezierPath()
  75. brow.move(to: NSPoint(x: 4.6, y: 6.4))
  76. brow.curve(to: NSPoint(x: 13.4, y: 6.4), controlPoint1: NSPoint(x: 6.2, y: 2.2), controlPoint2: NSPoint(x: 11.8, y: 2.2))
  77. brow.lineWidth = 1.35
  78. brow.lineCapStyle = .round
  79. brow.stroke()
  80. }
  81. }
  82. final class IndeedLogoView: NSView {
  83. private let imageView = NSImageView()
  84. private let variant: IndeedBrandLogo.Variant
  85. private var displayHeight: CGFloat
  86. private var sizeConstraints: [NSLayoutConstraint] = []
  87. override var intrinsicContentSize: NSSize {
  88. NSSize(width: displayHeight, height: displayHeight)
  89. }
  90. init(displayHeight: CGFloat = 44, variant: IndeedBrandLogo.Variant = .standard) {
  91. self.displayHeight = displayHeight
  92. self.variant = variant
  93. super.init(frame: .zero)
  94. setUp()
  95. }
  96. @available(*, unavailable)
  97. required init?(coder: NSCoder) {
  98. fatalError("init(coder:) has not been implemented")
  99. }
  100. func setDisplayHeight(_ height: CGFloat) {
  101. displayHeight = height
  102. sizeConstraints.forEach { $0.constant = height }
  103. invalidateIntrinsicContentSize()
  104. }
  105. private func setUp() {
  106. translatesAutoresizingMaskIntoConstraints = false
  107. setContentHuggingPriority(.required, for: .horizontal)
  108. setContentHuggingPriority(.required, for: .vertical)
  109. setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
  110. setContentCompressionResistancePriority(.defaultLow, for: .vertical)
  111. imageView.translatesAutoresizingMaskIntoConstraints = false
  112. imageView.imageScaling = .scaleProportionallyUpOrDown
  113. imageView.image = IndeedBrandLogo.logoImage(variant: variant)
  114. addSubview(imageView)
  115. let width = widthAnchor.constraint(equalToConstant: displayHeight)
  116. let height = heightAnchor.constraint(equalToConstant: displayHeight)
  117. sizeConstraints = [width, height]
  118. NSLayoutConstraint.activate(sizeConstraints + [
  119. imageView.leadingAnchor.constraint(equalTo: leadingAnchor),
  120. imageView.trailingAnchor.constraint(equalTo: trailingAnchor),
  121. imageView.topAnchor.constraint(equalTo: topAnchor),
  122. imageView.bottomAnchor.constraint(equalTo: bottomAnchor)
  123. ])
  124. setAccessibilityElement(true)
  125. setAccessibilityRole(.image)
  126. setAccessibilityLabel(AppMarketingLinks.displayName)
  127. }
  128. }