SidebarView.swift 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import Cocoa
  2. enum SidebarDestination: Int, CaseIterable {
  3. case home
  4. case scan
  5. case scanAndHome
  6. case settings
  7. }
  8. final class SidebarView: NSView, AppearanceRefreshable {
  9. private var appNameLabel: NSTextField!
  10. var onDestinationSelected: ((SidebarDestination) -> Void)?
  11. private var navItems: [SidebarNavItem] = []
  12. init() {
  13. super.init(frame: .zero)
  14. wantsLayer = true
  15. layer?.backgroundColor = AppTheme.sidebarBackground.cgColor
  16. translatesAutoresizingMaskIntoConstraints = false
  17. setup()
  18. }
  19. @available(*, unavailable)
  20. required init?(coder: NSCoder) { nil }
  21. func select(_ destination: SidebarDestination) {
  22. for (index, item) in navItems.enumerated() {
  23. item.isSelected = index == destination.rawValue
  24. }
  25. }
  26. func refreshAppearance() {
  27. layer?.backgroundColor = AppTheme.sidebarBackground.cgColor
  28. appNameLabel?.refreshThemeLabelColor()
  29. }
  30. private func setup() {
  31. let logoContainer = NSView()
  32. logoContainer.translatesAutoresizingMaskIntoConstraints = false
  33. let logoIcon = NSImageView()
  34. logoIcon.translatesAutoresizingMaskIntoConstraints = false
  35. if let image = NSImage(systemSymbolName: "printer.fill", accessibilityDescription: "Smart Printer") {
  36. let config = NSImage.SymbolConfiguration(pointSize: 28, weight: .medium)
  37. logoIcon.image = image.withSymbolConfiguration(config)
  38. }
  39. logoIcon.contentTintColor = AppTheme.purple
  40. appNameLabel = NSTextField.themeLabel("Smart Printer", style: .primary, font: AppTheme.semiboldFont(size: 15))
  41. appNameLabel.alignment = .center
  42. appNameLabel.translatesAutoresizingMaskIntoConstraints = false
  43. let navStack = NSStackView()
  44. navStack.orientation = .vertical
  45. navStack.spacing = 4
  46. navStack.alignment = .leading
  47. navStack.translatesAutoresizingMaskIntoConstraints = false
  48. let bottomNavStack = NSStackView()
  49. bottomNavStack.orientation = .vertical
  50. bottomNavStack.spacing = 4
  51. bottomNavStack.alignment = .leading
  52. bottomNavStack.translatesAutoresizingMaskIntoConstraints = false
  53. let homeItem = SidebarNavItem(title: "Home", symbolName: "house.fill", isSelected: true)
  54. let scanItem = SidebarNavItem(title: "Scan", symbolName: "viewfinder")
  55. let scanAndHomeItem = SidebarNavItem(title: "Premium", symbolName: "diamond.fill", accent: .premium)
  56. let settingsItem = SidebarNavItem(title: "Settings", symbolName: "gearshape")
  57. let mainNavItems = [homeItem, scanItem, scanAndHomeItem]
  58. navItems = mainNavItems + [settingsItem]
  59. for (index, item) in navItems.enumerated() {
  60. item.onClick = { [weak self] in
  61. guard let self, let destination = SidebarDestination(rawValue: index) else { return }
  62. self.select(destination)
  63. self.onDestinationSelected?(destination)
  64. }
  65. }
  66. for item in mainNavItems {
  67. navStack.addArrangedSubview(item)
  68. }
  69. bottomNavStack.addArrangedSubview(settingsItem)
  70. addSubview(logoContainer)
  71. logoContainer.addSubview(logoIcon)
  72. addSubview(appNameLabel)
  73. addSubview(navStack)
  74. addSubview(bottomNavStack)
  75. let divider = NSBox()
  76. divider.boxType = .separator
  77. divider.translatesAutoresizingMaskIntoConstraints = false
  78. addSubview(divider)
  79. NSLayoutConstraint.activate([
  80. widthAnchor.constraint(equalToConstant: AppTheme.sidebarWidth),
  81. logoContainer.topAnchor.constraint(equalTo: topAnchor, constant: 52),
  82. logoContainer.centerXAnchor.constraint(equalTo: centerXAnchor),
  83. logoContainer.widthAnchor.constraint(equalToConstant: 48),
  84. logoContainer.heightAnchor.constraint(equalToConstant: 48),
  85. logoIcon.centerXAnchor.constraint(equalTo: logoContainer.centerXAnchor),
  86. logoIcon.centerYAnchor.constraint(equalTo: logoContainer.centerYAnchor),
  87. logoIcon.widthAnchor.constraint(equalToConstant: 36),
  88. logoIcon.heightAnchor.constraint(equalToConstant: 36),
  89. appNameLabel.topAnchor.constraint(equalTo: logoContainer.bottomAnchor, constant: 8),
  90. appNameLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
  91. navStack.topAnchor.constraint(equalTo: appNameLabel.bottomAnchor, constant: 32),
  92. navStack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12),
  93. navStack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12),
  94. bottomNavStack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12),
  95. bottomNavStack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12),
  96. bottomNavStack.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -24),
  97. homeItem.widthAnchor.constraint(equalTo: navStack.widthAnchor),
  98. scanItem.widthAnchor.constraint(equalTo: navStack.widthAnchor),
  99. scanAndHomeItem.widthAnchor.constraint(equalTo: navStack.widthAnchor),
  100. settingsItem.widthAnchor.constraint(equalTo: bottomNavStack.widthAnchor),
  101. divider.trailingAnchor.constraint(equalTo: trailingAnchor),
  102. divider.topAnchor.constraint(equalTo: topAnchor),
  103. divider.bottomAnchor.constraint(equalTo: bottomAnchor),
  104. divider.widthAnchor.constraint(equalToConstant: 1),
  105. ])
  106. }
  107. }