Jelajahi Sumber

Narrow the default window and scale card icons dynamically.

Centralize window dimensions in AppTheme so the app opens at 680px while preserving the existing layout with responsive icon sizing.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 1 bulan lalu
induk
melakukan
0a655379e0

+ 2 - 2
smart_printer/AppDelegate.swift

@@ -23,9 +23,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
         window.styleMask.insert(.fullSizeContentView)
         window.isMovableByWindowBackground = true
         window.backgroundColor = AppTheme.background
-        window.setContentSize(NSSize(width: 1120, height: 720))
+        window.setContentSize(NSSize(width: AppTheme.windowWidth, height: AppTheme.windowHeight))
         window.center()
-        window.minSize = NSSize(width: 900, height: 600)
+        window.minSize = NSSize(width: AppTheme.windowMinWidth, height: AppTheme.windowMinHeight)
     }
 
     func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {

+ 23 - 1
smart_printer/AppTheme.swift

@@ -1,7 +1,29 @@
 import Cocoa
 
 enum AppTheme {
-    static let sidebarWidth: CGFloat = 220
+    static let windowWidth: CGFloat = 680
+    static let windowHeight: CGFloat = 720
+    static let windowMinWidth: CGFloat = 600
+    static let windowMinHeight: CGFloat = 600
+
+    static let sidebarWidth: CGFloat = 200
+    static let contentPadding: CGFloat = 20
+    static let quickStartSpacing: CGFloat = 12
+    static let featureGridSpacing: CGFloat = 10
+
+    static let quickStartIconMax: CGFloat = 80
+    static let quickStartIconMin: CGFloat = 56
+    static let featureIconMax: CGFloat = 56
+    static let featureIconMin: CGFloat = 36
+
+    static func quickStartIconSize(forCardWidth width: CGFloat) -> CGFloat {
+        min(quickStartIconMax, max(quickStartIconMin, width * 0.58))
+    }
+
+    static func featureIconSize(forCardWidth width: CGFloat) -> CGFloat {
+        min(featureIconMax, max(featureIconMin, width * 0.38))
+    }
+
     static let cornerRadius: CGFloat = 14
     static let cardCornerRadius: CGFloat = 20
     static let featureCardCornerRadius: CGFloat = 16

+ 2 - 2
smart_printer/Base.lproj/Main.storyboard

@@ -686,7 +686,7 @@
                     <window key="window" title="Smart Printer" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="YES" animationBehavior="default" id="IQv-IB-iLA">
                         <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
                         <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
-                        <rect key="contentRect" x="196" y="240" width="1120" height="720"/>
+                        <rect key="contentRect" x="196" y="240" width="680" height="720"/>
                         <rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
                         <connections>
                             <outlet property="delegate" destination="B8D-0N-5wS" id="98r-iN-zZc"/>
@@ -705,7 +705,7 @@
             <objects>
                 <viewController id="XfG-lQ-9wD" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
                     <view key="view" id="m2S-Jp-Qdl">
-                        <rect key="frame" x="0.0" y="0.0" width="1120" height="720"/>
+                        <rect key="frame" x="0.0" y="0.0" width="680" height="720"/>
                         <autoresizingMask key="autoresizingMask"/>
                     </view>
                 </viewController>

+ 60 - 28
smart_printer/UIComponents.swift

@@ -184,7 +184,12 @@ struct QuickStartCardData {
 }
 
 final class QuickStartCardView: NSView {
+    private let iconView: QuickStartIconView
+    private var iconWidthConstraint: NSLayoutConstraint!
+    private var iconHeightConstraint: NSLayoutConstraint!
+
     init(data: QuickStartCardData) {
+        iconView = QuickStartIconView(kind: data.iconKind)
         super.init(frame: .zero)
         translatesAutoresizingMaskIntoConstraints = false
 
@@ -211,8 +216,6 @@ final class QuickStartCardView: NSView {
         let wavePattern = WavePatternView()
         wavePattern.translatesAutoresizingMaskIntoConstraints = false
 
-        let iconView = QuickStartIconView(kind: data.iconKind)
-
         addSubview(gradient)
         gradient.addSubview(wavePattern)
         gradient.addSubview(titleLabel)
@@ -225,32 +228,44 @@ final class QuickStartCardView: NSView {
             gradient.trailingAnchor.constraint(equalTo: trailingAnchor),
             gradient.topAnchor.constraint(equalTo: topAnchor),
             gradient.bottomAnchor.constraint(equalTo: bottomAnchor),
-            heightAnchor.constraint(equalToConstant: 160),
+            heightAnchor.constraint(equalToConstant: 148),
 
-            titleLabel.leadingAnchor.constraint(equalTo: gradient.leadingAnchor, constant: 24),
-            titleLabel.topAnchor.constraint(equalTo: gradient.topAnchor, constant: 28),
+            titleLabel.leadingAnchor.constraint(equalTo: gradient.leadingAnchor, constant: 18),
+            titleLabel.topAnchor.constraint(equalTo: gradient.topAnchor, constant: 24),
 
             subtitleLabel.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor),
-            subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 6),
-            subtitleLabel.trailingAnchor.constraint(lessThanOrEqualTo: iconView.leadingAnchor, constant: -12),
+            subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4),
+            subtitleLabel.trailingAnchor.constraint(lessThanOrEqualTo: iconView.leadingAnchor, constant: -8),
 
             button.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor),
-            button.bottomAnchor.constraint(equalTo: gradient.bottomAnchor, constant: -24),
+            button.bottomAnchor.constraint(equalTo: gradient.bottomAnchor, constant: -20),
 
             wavePattern.trailingAnchor.constraint(equalTo: gradient.trailingAnchor),
             wavePattern.bottomAnchor.constraint(equalTo: gradient.bottomAnchor),
-            wavePattern.widthAnchor.constraint(equalToConstant: 120),
-            wavePattern.heightAnchor.constraint(equalToConstant: 80),
+            wavePattern.widthAnchor.constraint(equalToConstant: 100),
+            wavePattern.heightAnchor.constraint(equalToConstant: 70),
 
-            iconView.trailingAnchor.constraint(equalTo: gradient.trailingAnchor, constant: -12),
+            iconView.trailingAnchor.constraint(equalTo: gradient.trailingAnchor, constant: -8),
             iconView.centerYAnchor.constraint(equalTo: gradient.centerYAnchor),
-            iconView.widthAnchor.constraint(equalToConstant: 110),
-            iconView.heightAnchor.constraint(equalToConstant: 110),
         ])
+
+        iconWidthConstraint = iconView.widthAnchor.constraint(equalToConstant: AppTheme.quickStartIconMax)
+        iconHeightConstraint = iconView.heightAnchor.constraint(equalToConstant: AppTheme.quickStartIconMax)
+        iconWidthConstraint.isActive = true
+        iconHeightConstraint.isActive = true
     }
 
     @available(*, unavailable)
     required init?(coder: NSCoder) { nil }
+
+    override func layout() {
+        super.layout()
+        let size = AppTheme.quickStartIconSize(forCardWidth: bounds.width)
+        if iconWidthConstraint.constant != size {
+            iconWidthConstraint.constant = size
+            iconHeightConstraint.constant = size
+        }
+    }
 }
 
 // MARK: - Feature Card
@@ -262,16 +277,21 @@ struct FeatureCardData {
 }
 
 final class FeatureCardView: NSView {
+    private let iconView: FeatureIconView
+    private var iconWidthConstraint: NSLayoutConstraint!
+    private var iconHeightConstraint: NSLayoutConstraint!
+
     init(data: FeatureCardData) {
+        iconView = FeatureIconView(kind: data.iconKind)
         super.init(frame: .zero)
         translatesAutoresizingMaskIntoConstraints = false
+        setContentHuggingPriority(.defaultLow, for: .horizontal)
+        setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
         wantsLayer = true
         layer?.backgroundColor = AppTheme.cardBackground.cgColor
         layer?.cornerRadius = AppTheme.featureCardCornerRadius
         applyCardShadow()
 
-        let iconView = FeatureIconView(kind: data.iconKind)
-
         let titleLabel = NSTextField(labelWithString: data.title)
         titleLabel.font = AppTheme.semiboldFont(size: 15)
         titleLabel.textColor = AppTheme.textPrimary
@@ -286,7 +306,7 @@ final class FeatureCardView: NSView {
         arrowButton.isBordered = false
         arrowButton.wantsLayer = true
         arrowButton.layer?.backgroundColor = NSColor.white.cgColor
-        arrowButton.layer?.cornerRadius = 15
+        arrowButton.layer?.cornerRadius = 13
         arrowButton.layer?.borderWidth = 1
         arrowButton.layer?.borderColor = NSColor(calibratedWhite: 0.88, alpha: 1).cgColor
         arrowButton.translatesAutoresizingMaskIntoConstraints = false
@@ -302,31 +322,43 @@ final class FeatureCardView: NSView {
         addSubview(arrowButton)
 
         NSLayoutConstraint.activate([
-            heightAnchor.constraint(equalToConstant: 118),
+            heightAnchor.constraint(equalToConstant: 104),
 
-            iconView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 14),
+            iconView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10),
             iconView.centerYAnchor.constraint(equalTo: centerYAnchor),
-            iconView.widthAnchor.constraint(equalToConstant: 72),
-            iconView.heightAnchor.constraint(equalToConstant: 72),
 
-            titleLabel.leadingAnchor.constraint(equalTo: iconView.trailingAnchor, constant: 10),
-            titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 28),
-            titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -40),
+            titleLabel.leadingAnchor.constraint(equalTo: iconView.trailingAnchor, constant: 8),
+            titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 22),
+            titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -32),
 
             subtitleLabel.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor),
-            subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4),
+            subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 3),
             subtitleLabel.trailingAnchor.constraint(equalTo: titleLabel.trailingAnchor),
 
-            arrowButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -14),
-            arrowButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -14),
-            arrowButton.widthAnchor.constraint(equalToConstant: 30),
-            arrowButton.heightAnchor.constraint(equalToConstant: 30),
+            arrowButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10),
+            arrowButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10),
+            arrowButton.widthAnchor.constraint(equalToConstant: 26),
+            arrowButton.heightAnchor.constraint(equalToConstant: 26),
         ])
+
+        iconWidthConstraint = iconView.widthAnchor.constraint(equalToConstant: AppTheme.featureIconMax)
+        iconHeightConstraint = iconView.heightAnchor.constraint(equalToConstant: AppTheme.featureIconMax)
+        iconWidthConstraint.isActive = true
+        iconHeightConstraint.isActive = true
     }
 
     @available(*, unavailable)
     required init?(coder: NSCoder) { nil }
 
+    override func layout() {
+        super.layout()
+        let size = AppTheme.featureIconSize(forCardWidth: bounds.width)
+        if iconWidthConstraint.constant != size {
+            iconWidthConstraint.constant = size
+            iconHeightConstraint.constant = size
+        }
+    }
+
     override func resetCursorRects() {
         addCursorRect(bounds, cursor: .pointingHand)
     }

+ 51 - 37
smart_printer/ViewController.swift

@@ -8,7 +8,7 @@ import Cocoa
 class ViewController: NSViewController {
 
     override func loadView() {
-        let container = NSView(frame: NSRect(x: 0, y: 0, width: 1120, height: 720))
+        let container = NSView(frame: NSRect(x: 0, y: 0, width: AppTheme.windowWidth, height: AppTheme.windowHeight))
         container.autoresizingMask = [.width, .height]
         container.wantsLayer = true
         container.layer?.backgroundColor = AppTheme.background.cgColor
@@ -62,8 +62,8 @@ class ViewController: NSViewController {
 
             wavePattern.trailingAnchor.constraint(equalTo: mainContent.trailingAnchor),
             wavePattern.bottomAnchor.constraint(equalTo: mainContent.bottomAnchor),
-            wavePattern.widthAnchor.constraint(equalToConstant: 280),
-            wavePattern.heightAnchor.constraint(equalToConstant: 180),
+            wavePattern.widthAnchor.constraint(equalToConstant: 200),
+            wavePattern.heightAnchor.constraint(equalToConstant: 140),
         ])
     }
 
@@ -129,12 +129,12 @@ class ViewController: NSViewController {
             documentView.trailingAnchor.constraint(equalTo: contentGuide.trailingAnchor),
             documentView.widthAnchor.constraint(equalTo: contentGuide.widthAnchor),
 
-            quickStartSection.leadingAnchor.constraint(equalTo: documentView.leadingAnchor, constant: 32),
-            quickStartSection.trailingAnchor.constraint(equalTo: documentView.trailingAnchor, constant: -32),
+            quickStartSection.leadingAnchor.constraint(equalTo: documentView.leadingAnchor, constant: AppTheme.contentPadding),
+            quickStartSection.trailingAnchor.constraint(equalTo: documentView.trailingAnchor, constant: -AppTheme.contentPadding),
             quickStartSection.topAnchor.constraint(equalTo: documentView.topAnchor, constant: 8),
 
-            createPrintSection.leadingAnchor.constraint(equalTo: documentView.leadingAnchor, constant: 32),
-            createPrintSection.trailingAnchor.constraint(equalTo: documentView.trailingAnchor, constant: -32),
+            createPrintSection.leadingAnchor.constraint(equalTo: documentView.leadingAnchor, constant: AppTheme.contentPadding),
+            createPrintSection.trailingAnchor.constraint(equalTo: documentView.trailingAnchor, constant: -AppTheme.contentPadding),
             createPrintSection.topAnchor.constraint(equalTo: quickStartSection.bottomAnchor, constant: 36),
             createPrintSection.bottomAnchor.constraint(equalTo: documentView.bottomAnchor, constant: -40),
         ])
@@ -192,7 +192,7 @@ class ViewController: NSViewController {
 
         let cardsStack = NSStackView()
         cardsStack.orientation = .horizontal
-        cardsStack.spacing = 20
+        cardsStack.spacing = AppTheme.quickStartSpacing
         cardsStack.distribution = .fillEqually
         cardsStack.translatesAutoresizingMaskIntoConstraints = false
 
@@ -258,51 +258,65 @@ class ViewController: NSViewController {
             FeatureCardData(title: "OCR File", subtitle: "Scan and print text from images", iconKind: .ocrFile),
         ]
 
-        let row1 = makeFeatureRow(features: Array(features[0..<4]), columns: 4)
-        let row2 = makeFeatureRow(features: Array(features[4..<6]), columns: 4)
-
-        let gridStack = NSStackView(views: [row1, row2])
-        gridStack.orientation = .vertical
-        gridStack.spacing = 16
-        gridStack.distribution = .fill
-        gridStack.translatesAutoresizingMaskIntoConstraints = false
-        section.addSubview(gridStack)
+        let grid = makeFeatureGrid(features: features, columns: 4)
+        section.addSubview(grid)
 
         NSLayoutConstraint.activate([
             title.leadingAnchor.constraint(equalTo: section.leadingAnchor),
             title.topAnchor.constraint(equalTo: section.topAnchor),
 
-            gridStack.leadingAnchor.constraint(equalTo: section.leadingAnchor),
-            gridStack.trailingAnchor.constraint(equalTo: section.trailingAnchor),
-            gridStack.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 16),
-            gridStack.bottomAnchor.constraint(equalTo: section.bottomAnchor),
+            grid.leadingAnchor.constraint(equalTo: section.leadingAnchor),
+            grid.trailingAnchor.constraint(equalTo: section.trailingAnchor),
+            grid.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 16),
+            grid.bottomAnchor.constraint(equalTo: section.bottomAnchor),
         ])
 
         return section
     }
 
-    private func makeFeatureRow(features: [FeatureCardData], columns: Int) -> NSStackView {
-        let row = NSStackView()
-        row.orientation = .horizontal
-        row.spacing = 16
-        row.distribution = .fillEqually
-        row.alignment = .centerY
-        row.translatesAutoresizingMaskIntoConstraints = false
+    private func makeFeatureGrid(features: [FeatureCardData], columns: Int) -> NSView {
+        let grid = NSView()
+        grid.translatesAutoresizingMaskIntoConstraints = false
+
+        let spacing = AppTheme.featureGridSpacing
+        let cards = features.map { FeatureCardView(data: $0) }
+        let rowCount = (cards.count + columns - 1) / columns
 
-        for feature in features {
-            row.addArrangedSubview(FeatureCardView(data: feature))
+        for card in cards {
+            grid.addSubview(card)
         }
 
-        if features.count < columns {
-            let spacerCount = columns - features.count
-            for _ in 0..<spacerCount {
-                let spacer = NSView()
-                spacer.translatesAutoresizingMaskIntoConstraints = false
-                row.addArrangedSubview(spacer)
+        for (index, card) in cards.enumerated() {
+            let row = index / columns
+            let col = index % columns
+            let columnAnchor = cards[col]
+
+            if col == 0 {
+                card.leadingAnchor.constraint(equalTo: grid.leadingAnchor).isActive = true
+            } else {
+                let leftCard = cards[index - 1]
+                card.leadingAnchor.constraint(equalTo: leftCard.trailingAnchor, constant: spacing).isActive = true
+            }
+
+            card.widthAnchor.constraint(equalTo: columnAnchor.widthAnchor).isActive = true
+
+            if row == 0 {
+                card.topAnchor.constraint(equalTo: grid.topAnchor).isActive = true
+            } else {
+                let aboveCard = cards[index - columns]
+                card.topAnchor.constraint(equalTo: aboveCard.bottomAnchor, constant: spacing).isActive = true
+            }
+
+            if col == columns - 1 {
+                card.trailingAnchor.constraint(equalTo: grid.trailingAnchor).isActive = true
+            }
+
+            if row == rowCount - 1 {
+                card.bottomAnchor.constraint(equalTo: grid.bottomAnchor).isActive = true
             }
         }
 
-        return row
+        return grid
     }
 }