Эх сурвалжийг харах

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 сар өмнө
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 {