Sfoglia il codice sorgente

Make sidebar navigation clickable with active selection styling.

Switch main content between Home, Scan, and Premium tabs so each sidebar item is functional while Premium content remains a placeholder.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 1 mese fa
parent
commit
3d8b6f4dc5

+ 30 - 8
smart_printer/SidebarView.swift

@@ -1,6 +1,16 @@
 import Cocoa
 
+enum SidebarDestination: Int, CaseIterable {
+    case home
+    case scan
+    case scanAndHome
+}
+
 final class SidebarView: NSView {
+    var onDestinationSelected: ((SidebarDestination) -> Void)?
+
+    private var navItems: [SidebarNavItem] = []
+
     init() {
         super.init(frame: .zero)
         wantsLayer = true
@@ -12,6 +22,12 @@ final class SidebarView: NSView {
     @available(*, unavailable)
     required init?(coder: NSCoder) { nil }
 
+    func select(_ destination: SidebarDestination) {
+        for (index, item) in navItems.enumerated() {
+            item.isSelected = index == destination.rawValue
+        }
+    }
+
     private func setup() {
         let logoContainer = NSView()
         logoContainer.translatesAutoresizingMaskIntoConstraints = false
@@ -36,13 +52,19 @@ final class SidebarView: NSView {
         navStack.alignment = .leading
         navStack.translatesAutoresizingMaskIntoConstraints = false
 
-        let homeItem = SidebarNavItem(title: "Home", symbolName: "house.fill", style: .active)
-        let scanItem = SidebarNavItem(title: "Scan", symbolName: "viewfinder", style: .normal)
-        let premiumItem = SidebarNavItem(title: "Premium", symbolName: "diamond.fill", style: .premium)
-
-        navStack.addArrangedSubview(homeItem)
-        navStack.addArrangedSubview(scanItem)
-        navStack.addArrangedSubview(premiumItem)
+        let homeItem = SidebarNavItem(title: "Home", symbolName: "house.fill", isSelected: true)
+        let scanItem = SidebarNavItem(title: "Scan", symbolName: "viewfinder")
+        let scanAndHomeItem = SidebarNavItem(title: "Premium", symbolName: "diamond.fill")
+
+        navItems = [homeItem, scanItem, scanAndHomeItem]
+        for (index, item) in navItems.enumerated() {
+            item.onClick = { [weak self] in
+                guard let self, let destination = SidebarDestination(rawValue: index) else { return }
+                self.select(destination)
+                self.onDestinationSelected?(destination)
+            }
+            navStack.addArrangedSubview(item)
+        }
 
         addSubview(logoContainer)
         logoContainer.addSubview(logoIcon)
@@ -76,7 +98,7 @@ final class SidebarView: NSView {
 
             homeItem.widthAnchor.constraint(equalTo: navStack.widthAnchor),
             scanItem.widthAnchor.constraint(equalTo: navStack.widthAnchor),
-            premiumItem.widthAnchor.constraint(equalTo: navStack.widthAnchor),
+            scanAndHomeItem.widthAnchor.constraint(equalTo: navStack.widthAnchor),
 
             divider.trailingAnchor.constraint(equalTo: trailingAnchor),
             divider.topAnchor.constraint(equalTo: topAnchor),

+ 14 - 9
smart_printer/UIComponents.swift

@@ -61,17 +61,21 @@ final class SidebarNavItem: NSControl {
     enum Style {
         case normal
         case active
-        case premium
     }
 
+    var onClick: (() -> Void)?
+
     private let iconView = NSImageView()
     private let titleLabel = NSTextField(labelWithString: "")
     private let container = NSView()
-    private var style: Style = .normal
 
-    init(title: String, symbolName: String, style: Style = .normal) {
+    var isSelected: Bool = false {
+        didSet { applyStyle(isSelected ? .active : .normal) }
+    }
+
+    init(title: String, symbolName: String, isSelected: Bool = false) {
         super.init(frame: .zero)
-        self.style = style
+        self.isSelected = isSelected
 
         titleLabel.stringValue = title
         titleLabel.font = AppTheme.mediumFont(size: 14)
@@ -110,7 +114,7 @@ final class SidebarNavItem: NSControl {
             titleLabel.trailingAnchor.constraint(lessThanOrEqualTo: container.trailingAnchor, constant: -12),
         ])
 
-        applyStyle(style)
+        applyStyle(isSelected ? .active : .normal)
     }
 
     @available(*, unavailable)
@@ -129,13 +133,14 @@ final class SidebarNavItem: NSControl {
             container.layer?.backgroundColor = AppTheme.homeActiveBackground.cgColor
             titleLabel.textColor = AppTheme.homeActiveForeground
             iconView.contentTintColor = AppTheme.homeActiveForeground
-        case .premium:
-            container.layer?.backgroundColor = AppTheme.premiumBackground.cgColor
-            titleLabel.textColor = AppTheme.premiumForeground
-            iconView.contentTintColor = AppTheme.premiumForeground
         }
     }
 
+    override func mouseUp(with event: NSEvent) {
+        guard bounds.contains(convert(event.locationInWindow, from: nil)) else { return }
+        onClick?()
+    }
+
     override func resetCursorRects() {
         addCursorRect(bounds, cursor: .pointingHand)
     }

+ 136 - 7
smart_printer/ViewController.swift

@@ -7,6 +7,12 @@ import Cocoa
 
 class ViewController: NSViewController {
 
+    private var sidebar: SidebarView!
+    private var homeContentView: NSView!
+    private var scanContentView: NSView!
+    private var scanAndHomeContentView: NSView!
+    private var contentContainer: NSView!
+
     override func loadView() {
         let container = NSView(frame: NSRect(x: 0, y: 0, width: AppTheme.windowWidth, height: AppTheme.windowHeight))
         container.autoresizingMask = [.width, .height]
@@ -21,7 +27,7 @@ class ViewController: NSViewController {
     }
 
     private func setupLayout() {
-        let sidebar = SidebarView()
+        sidebar = SidebarView()
 
         let mainContent = NSView()
         mainContent.translatesAutoresizingMaskIntoConstraints = false
@@ -29,7 +35,17 @@ class ViewController: NSViewController {
         mainContent.layer?.backgroundColor = AppTheme.background.cgColor
 
         let header = makeHeader()
-        let scrollView = makeScrollView()
+
+        contentContainer = NSView()
+        contentContainer.translatesAutoresizingMaskIntoConstraints = false
+
+        homeContentView = makeHomeContentView()
+        scanContentView = makeScanContentView()
+        scanAndHomeContentView = makeScanAndHomeContentView()
+
+        contentContainer.addSubview(homeContentView)
+        contentContainer.addSubview(scanContentView)
+        contentContainer.addSubview(scanAndHomeContentView)
 
         let wavePattern = WavePatternView()
         wavePattern.translatesAutoresizingMaskIntoConstraints = false
@@ -37,9 +53,13 @@ class ViewController: NSViewController {
         view.addSubview(sidebar)
         view.addSubview(mainContent)
         mainContent.addSubview(header)
-        mainContent.addSubview(scrollView)
+        mainContent.addSubview(contentContainer)
         mainContent.addSubview(wavePattern)
 
+        sidebar.onDestinationSelected = { [weak self] destination in
+            self?.showDestination(destination)
+        }
+
         NSLayoutConstraint.activate([
             sidebar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
             sidebar.topAnchor.constraint(equalTo: view.topAnchor),
@@ -55,16 +75,125 @@ class ViewController: NSViewController {
             header.topAnchor.constraint(equalTo: mainContent.topAnchor, constant: 16),
             header.heightAnchor.constraint(equalToConstant: 44),
 
-            scrollView.leadingAnchor.constraint(equalTo: mainContent.leadingAnchor),
-            scrollView.trailingAnchor.constraint(equalTo: mainContent.trailingAnchor),
-            scrollView.topAnchor.constraint(equalTo: header.bottomAnchor, constant: 8),
-            scrollView.bottomAnchor.constraint(equalTo: mainContent.bottomAnchor),
+            contentContainer.leadingAnchor.constraint(equalTo: mainContent.leadingAnchor),
+            contentContainer.trailingAnchor.constraint(equalTo: mainContent.trailingAnchor),
+            contentContainer.topAnchor.constraint(equalTo: header.bottomAnchor, constant: 8),
+            contentContainer.bottomAnchor.constraint(equalTo: mainContent.bottomAnchor),
 
             wavePattern.trailingAnchor.constraint(equalTo: mainContent.trailingAnchor),
             wavePattern.bottomAnchor.constraint(equalTo: mainContent.bottomAnchor),
             wavePattern.widthAnchor.constraint(equalToConstant: 200),
             wavePattern.heightAnchor.constraint(equalToConstant: 140),
         ])
+
+        pinContentView(homeContentView)
+        pinContentView(scanContentView)
+        pinContentView(scanAndHomeContentView)
+        showDestination(.home)
+    }
+
+    private func pinContentView(_ contentView: NSView) {
+        NSLayoutConstraint.activate([
+            contentView.leadingAnchor.constraint(equalTo: contentContainer.leadingAnchor),
+            contentView.trailingAnchor.constraint(equalTo: contentContainer.trailingAnchor),
+            contentView.topAnchor.constraint(equalTo: contentContainer.topAnchor),
+            contentView.bottomAnchor.constraint(equalTo: contentContainer.bottomAnchor),
+        ])
+    }
+
+    private func showDestination(_ destination: SidebarDestination) {
+        homeContentView.isHidden = destination != .home
+        scanContentView.isHidden = destination != .scan
+        scanAndHomeContentView.isHidden = destination != .scanAndHome
+    }
+
+    private func makeHomeContentView() -> NSView {
+        let container = NSView()
+        container.translatesAutoresizingMaskIntoConstraints = false
+
+        let scrollView = makeScrollView()
+        container.addSubview(scrollView)
+
+        NSLayoutConstraint.activate([
+            scrollView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
+            scrollView.trailingAnchor.constraint(equalTo: container.trailingAnchor),
+            scrollView.topAnchor.constraint(equalTo: container.topAnchor),
+            scrollView.bottomAnchor.constraint(equalTo: container.bottomAnchor),
+        ])
+
+        return container
+    }
+
+    private func makeScanContentView() -> NSView {
+        makeDestinationScrollView(
+            title: "Scan",
+            features: [
+                FeatureCardData(title: "Scan File", subtitle: "Scan any document", iconKind: .scanFile),
+                FeatureCardData(title: "OCR File", subtitle: "Scan and print text from images", iconKind: .ocrFile),
+            ]
+        )
+    }
+
+    private func makeScanAndHomeContentView() -> NSView {
+        makeDestinationScrollView(
+            title: "Premium",
+            features: [
+                FeatureCardData(title: "From Photos", subtitle: "Take a photo from gallery", iconKind: .scanFile),
+                FeatureCardData(title: "Scan File", subtitle: "Scan any document", iconKind: .scanFile),
+                FeatureCardData(title: "Print Text", subtitle: "Type text and print", iconKind: .printText),
+                FeatureCardData(title: "Print Contacts", subtitle: "Print your contacts", iconKind: .printContacts),
+            ]
+        )
+    }
+
+    private func makeDestinationScrollView(title: String, features: [FeatureCardData]) -> NSView {
+        let container = NSView()
+        container.translatesAutoresizingMaskIntoConstraints = false
+        container.isHidden = true
+
+        let scrollView = NSScrollView()
+        scrollView.translatesAutoresizingMaskIntoConstraints = false
+        scrollView.hasVerticalScroller = true
+        scrollView.hasHorizontalScroller = false
+        scrollView.autohidesScrollers = true
+        scrollView.drawsBackground = false
+        scrollView.borderType = .noBorder
+
+        let documentView = FlippedDocumentView()
+        documentView.translatesAutoresizingMaskIntoConstraints = false
+
+        let sectionTitle = makeSectionTitle(title, showGridIcon: true)
+        let grid = makeFeatureRow(features: features)
+
+        documentView.addSubview(sectionTitle)
+        documentView.addSubview(grid)
+        scrollView.documentView = documentView
+        container.addSubview(scrollView)
+
+        let contentGuide = scrollView.contentView
+
+        NSLayoutConstraint.activate([
+            scrollView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
+            scrollView.trailingAnchor.constraint(equalTo: container.trailingAnchor),
+            scrollView.topAnchor.constraint(equalTo: container.topAnchor),
+            scrollView.bottomAnchor.constraint(equalTo: container.bottomAnchor),
+
+            documentView.topAnchor.constraint(equalTo: contentGuide.topAnchor),
+            documentView.leadingAnchor.constraint(equalTo: contentGuide.leadingAnchor),
+            documentView.trailingAnchor.constraint(equalTo: contentGuide.trailingAnchor),
+            documentView.widthAnchor.constraint(equalTo: contentGuide.widthAnchor),
+
+            sectionTitle.leadingAnchor.constraint(equalTo: documentView.leadingAnchor, constant: AppTheme.contentPadding),
+            sectionTitle.trailingAnchor.constraint(equalTo: documentView.trailingAnchor, constant: -AppTheme.contentPadding),
+            sectionTitle.topAnchor.constraint(equalTo: documentView.topAnchor, constant: 8),
+
+            grid.leadingAnchor.constraint(equalTo: documentView.leadingAnchor, constant: AppTheme.contentPadding),
+            grid.trailingAnchor.constraint(equalTo: documentView.trailingAnchor, constant: -AppTheme.contentPadding),
+            grid.topAnchor.constraint(equalTo: sectionTitle.bottomAnchor, constant: 16),
+            grid.bottomAnchor.constraint(equalTo: documentView.bottomAnchor, constant: -40),
+        ])
+
+        return container
     }
 
     private func makeHeader() -> NSView {