소스 검색

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 17 시간 전
부모
커밋
0a655379e0
5개의 변경된 파일138개의 추가작업 그리고 70개의 파일을 삭제
  1. 2 2
      smart_printer/AppDelegate.swift
  2. 23 1
      smart_printer/AppTheme.swift
  3. 2 2
      smart_printer/Base.lproj/Main.storyboard
  4. 60 28
      smart_printer/UIComponents.swift
  5. 51 37
      smart_printer/ViewController.swift

+ 2 - 2
smart_printer/AppDelegate.swift

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

+ 23 - 1
smart_printer/AppTheme.swift

@@ -1,7 +1,29 @@
1 1
 import Cocoa
2 2
 
3 3
 enum AppTheme {
4
-    static let sidebarWidth: CGFloat = 220
4
+    static let windowWidth: CGFloat = 680
5
+    static let windowHeight: CGFloat = 720
6
+    static let windowMinWidth: CGFloat = 600
7
+    static let windowMinHeight: CGFloat = 600
8
+
9
+    static let sidebarWidth: CGFloat = 200
10
+    static let contentPadding: CGFloat = 20
11
+    static let quickStartSpacing: CGFloat = 12
12
+    static let featureGridSpacing: CGFloat = 10
13
+
14
+    static let quickStartIconMax: CGFloat = 80
15
+    static let quickStartIconMin: CGFloat = 56
16
+    static let featureIconMax: CGFloat = 56
17
+    static let featureIconMin: CGFloat = 36
18
+
19
+    static func quickStartIconSize(forCardWidth width: CGFloat) -> CGFloat {
20
+        min(quickStartIconMax, max(quickStartIconMin, width * 0.58))
21
+    }
22
+
23
+    static func featureIconSize(forCardWidth width: CGFloat) -> CGFloat {
24
+        min(featureIconMax, max(featureIconMin, width * 0.38))
25
+    }
26
+
5 27
     static let cornerRadius: CGFloat = 14
6 28
     static let cardCornerRadius: CGFloat = 20
7 29
     static let featureCardCornerRadius: CGFloat = 16

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

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

+ 60 - 28
smart_printer/UIComponents.swift

@@ -184,7 +184,12 @@ struct QuickStartCardData {
184 184
 }
185 185
 
186 186
 final class QuickStartCardView: NSView {
187
+    private let iconView: QuickStartIconView
188
+    private var iconWidthConstraint: NSLayoutConstraint!
189
+    private var iconHeightConstraint: NSLayoutConstraint!
190
+
187 191
     init(data: QuickStartCardData) {
192
+        iconView = QuickStartIconView(kind: data.iconKind)
188 193
         super.init(frame: .zero)
189 194
         translatesAutoresizingMaskIntoConstraints = false
190 195
 
@@ -211,8 +216,6 @@ final class QuickStartCardView: NSView {
211 216
         let wavePattern = WavePatternView()
212 217
         wavePattern.translatesAutoresizingMaskIntoConstraints = false
213 218
 
214
-        let iconView = QuickStartIconView(kind: data.iconKind)
215
-
216 219
         addSubview(gradient)
217 220
         gradient.addSubview(wavePattern)
218 221
         gradient.addSubview(titleLabel)
@@ -225,32 +228,44 @@ final class QuickStartCardView: NSView {
225 228
             gradient.trailingAnchor.constraint(equalTo: trailingAnchor),
226 229
             gradient.topAnchor.constraint(equalTo: topAnchor),
227 230
             gradient.bottomAnchor.constraint(equalTo: bottomAnchor),
228
-            heightAnchor.constraint(equalToConstant: 160),
231
+            heightAnchor.constraint(equalToConstant: 148),
229 232
 
230
-            titleLabel.leadingAnchor.constraint(equalTo: gradient.leadingAnchor, constant: 24),
231
-            titleLabel.topAnchor.constraint(equalTo: gradient.topAnchor, constant: 28),
233
+            titleLabel.leadingAnchor.constraint(equalTo: gradient.leadingAnchor, constant: 18),
234
+            titleLabel.topAnchor.constraint(equalTo: gradient.topAnchor, constant: 24),
232 235
 
233 236
             subtitleLabel.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor),
234
-            subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 6),
235
-            subtitleLabel.trailingAnchor.constraint(lessThanOrEqualTo: iconView.leadingAnchor, constant: -12),
237
+            subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4),
238
+            subtitleLabel.trailingAnchor.constraint(lessThanOrEqualTo: iconView.leadingAnchor, constant: -8),
236 239
 
237 240
             button.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor),
238
-            button.bottomAnchor.constraint(equalTo: gradient.bottomAnchor, constant: -24),
241
+            button.bottomAnchor.constraint(equalTo: gradient.bottomAnchor, constant: -20),
239 242
 
240 243
             wavePattern.trailingAnchor.constraint(equalTo: gradient.trailingAnchor),
241 244
             wavePattern.bottomAnchor.constraint(equalTo: gradient.bottomAnchor),
242
-            wavePattern.widthAnchor.constraint(equalToConstant: 120),
243
-            wavePattern.heightAnchor.constraint(equalToConstant: 80),
245
+            wavePattern.widthAnchor.constraint(equalToConstant: 100),
246
+            wavePattern.heightAnchor.constraint(equalToConstant: 70),
244 247
 
245
-            iconView.trailingAnchor.constraint(equalTo: gradient.trailingAnchor, constant: -12),
248
+            iconView.trailingAnchor.constraint(equalTo: gradient.trailingAnchor, constant: -8),
246 249
             iconView.centerYAnchor.constraint(equalTo: gradient.centerYAnchor),
247
-            iconView.widthAnchor.constraint(equalToConstant: 110),
248
-            iconView.heightAnchor.constraint(equalToConstant: 110),
249 250
         ])
251
+
252
+        iconWidthConstraint = iconView.widthAnchor.constraint(equalToConstant: AppTheme.quickStartIconMax)
253
+        iconHeightConstraint = iconView.heightAnchor.constraint(equalToConstant: AppTheme.quickStartIconMax)
254
+        iconWidthConstraint.isActive = true
255
+        iconHeightConstraint.isActive = true
250 256
     }
251 257
 
252 258
     @available(*, unavailable)
253 259
     required init?(coder: NSCoder) { nil }
260
+
261
+    override func layout() {
262
+        super.layout()
263
+        let size = AppTheme.quickStartIconSize(forCardWidth: bounds.width)
264
+        if iconWidthConstraint.constant != size {
265
+            iconWidthConstraint.constant = size
266
+            iconHeightConstraint.constant = size
267
+        }
268
+    }
254 269
 }
255 270
 
256 271
 // MARK: - Feature Card
@@ -262,16 +277,21 @@ struct FeatureCardData {
262 277
 }
263 278
 
264 279
 final class FeatureCardView: NSView {
280
+    private let iconView: FeatureIconView
281
+    private var iconWidthConstraint: NSLayoutConstraint!
282
+    private var iconHeightConstraint: NSLayoutConstraint!
283
+
265 284
     init(data: FeatureCardData) {
285
+        iconView = FeatureIconView(kind: data.iconKind)
266 286
         super.init(frame: .zero)
267 287
         translatesAutoresizingMaskIntoConstraints = false
288
+        setContentHuggingPriority(.defaultLow, for: .horizontal)
289
+        setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
268 290
         wantsLayer = true
269 291
         layer?.backgroundColor = AppTheme.cardBackground.cgColor
270 292
         layer?.cornerRadius = AppTheme.featureCardCornerRadius
271 293
         applyCardShadow()
272 294
 
273
-        let iconView = FeatureIconView(kind: data.iconKind)
274
-
275 295
         let titleLabel = NSTextField(labelWithString: data.title)
276 296
         titleLabel.font = AppTheme.semiboldFont(size: 15)
277 297
         titleLabel.textColor = AppTheme.textPrimary
@@ -286,7 +306,7 @@ final class FeatureCardView: NSView {
286 306
         arrowButton.isBordered = false
287 307
         arrowButton.wantsLayer = true
288 308
         arrowButton.layer?.backgroundColor = NSColor.white.cgColor
289
-        arrowButton.layer?.cornerRadius = 15
309
+        arrowButton.layer?.cornerRadius = 13
290 310
         arrowButton.layer?.borderWidth = 1
291 311
         arrowButton.layer?.borderColor = NSColor(calibratedWhite: 0.88, alpha: 1).cgColor
292 312
         arrowButton.translatesAutoresizingMaskIntoConstraints = false
@@ -302,31 +322,43 @@ final class FeatureCardView: NSView {
302 322
         addSubview(arrowButton)
303 323
 
304 324
         NSLayoutConstraint.activate([
305
-            heightAnchor.constraint(equalToConstant: 118),
325
+            heightAnchor.constraint(equalToConstant: 104),
306 326
 
307
-            iconView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 14),
327
+            iconView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10),
308 328
             iconView.centerYAnchor.constraint(equalTo: centerYAnchor),
309
-            iconView.widthAnchor.constraint(equalToConstant: 72),
310
-            iconView.heightAnchor.constraint(equalToConstant: 72),
311 329
 
312
-            titleLabel.leadingAnchor.constraint(equalTo: iconView.trailingAnchor, constant: 10),
313
-            titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 28),
314
-            titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -40),
330
+            titleLabel.leadingAnchor.constraint(equalTo: iconView.trailingAnchor, constant: 8),
331
+            titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 22),
332
+            titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -32),
315 333
 
316 334
             subtitleLabel.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor),
317
-            subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4),
335
+            subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 3),
318 336
             subtitleLabel.trailingAnchor.constraint(equalTo: titleLabel.trailingAnchor),
319 337
 
320
-            arrowButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -14),
321
-            arrowButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -14),
322
-            arrowButton.widthAnchor.constraint(equalToConstant: 30),
323
-            arrowButton.heightAnchor.constraint(equalToConstant: 30),
338
+            arrowButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10),
339
+            arrowButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10),
340
+            arrowButton.widthAnchor.constraint(equalToConstant: 26),
341
+            arrowButton.heightAnchor.constraint(equalToConstant: 26),
324 342
         ])
343
+
344
+        iconWidthConstraint = iconView.widthAnchor.constraint(equalToConstant: AppTheme.featureIconMax)
345
+        iconHeightConstraint = iconView.heightAnchor.constraint(equalToConstant: AppTheme.featureIconMax)
346
+        iconWidthConstraint.isActive = true
347
+        iconHeightConstraint.isActive = true
325 348
     }
326 349
 
327 350
     @available(*, unavailable)
328 351
     required init?(coder: NSCoder) { nil }
329 352
 
353
+    override func layout() {
354
+        super.layout()
355
+        let size = AppTheme.featureIconSize(forCardWidth: bounds.width)
356
+        if iconWidthConstraint.constant != size {
357
+            iconWidthConstraint.constant = size
358
+            iconHeightConstraint.constant = size
359
+        }
360
+    }
361
+
330 362
     override func resetCursorRects() {
331 363
         addCursorRect(bounds, cursor: .pointingHand)
332 364
     }

+ 51 - 37
smart_printer/ViewController.swift

@@ -8,7 +8,7 @@ import Cocoa
8 8
 class ViewController: NSViewController {
9 9
 
10 10
     override func loadView() {
11
-        let container = NSView(frame: NSRect(x: 0, y: 0, width: 1120, height: 720))
11
+        let container = NSView(frame: NSRect(x: 0, y: 0, width: AppTheme.windowWidth, height: AppTheme.windowHeight))
12 12
         container.autoresizingMask = [.width, .height]
13 13
         container.wantsLayer = true
14 14
         container.layer?.backgroundColor = AppTheme.background.cgColor
@@ -62,8 +62,8 @@ class ViewController: NSViewController {
62 62
 
63 63
             wavePattern.trailingAnchor.constraint(equalTo: mainContent.trailingAnchor),
64 64
             wavePattern.bottomAnchor.constraint(equalTo: mainContent.bottomAnchor),
65
-            wavePattern.widthAnchor.constraint(equalToConstant: 280),
66
-            wavePattern.heightAnchor.constraint(equalToConstant: 180),
65
+            wavePattern.widthAnchor.constraint(equalToConstant: 200),
66
+            wavePattern.heightAnchor.constraint(equalToConstant: 140),
67 67
         ])
68 68
     }
69 69
 
@@ -129,12 +129,12 @@ class ViewController: NSViewController {
129 129
             documentView.trailingAnchor.constraint(equalTo: contentGuide.trailingAnchor),
130 130
             documentView.widthAnchor.constraint(equalTo: contentGuide.widthAnchor),
131 131
 
132
-            quickStartSection.leadingAnchor.constraint(equalTo: documentView.leadingAnchor, constant: 32),
133
-            quickStartSection.trailingAnchor.constraint(equalTo: documentView.trailingAnchor, constant: -32),
132
+            quickStartSection.leadingAnchor.constraint(equalTo: documentView.leadingAnchor, constant: AppTheme.contentPadding),
133
+            quickStartSection.trailingAnchor.constraint(equalTo: documentView.trailingAnchor, constant: -AppTheme.contentPadding),
134 134
             quickStartSection.topAnchor.constraint(equalTo: documentView.topAnchor, constant: 8),
135 135
 
136
-            createPrintSection.leadingAnchor.constraint(equalTo: documentView.leadingAnchor, constant: 32),
137
-            createPrintSection.trailingAnchor.constraint(equalTo: documentView.trailingAnchor, constant: -32),
136
+            createPrintSection.leadingAnchor.constraint(equalTo: documentView.leadingAnchor, constant: AppTheme.contentPadding),
137
+            createPrintSection.trailingAnchor.constraint(equalTo: documentView.trailingAnchor, constant: -AppTheme.contentPadding),
138 138
             createPrintSection.topAnchor.constraint(equalTo: quickStartSection.bottomAnchor, constant: 36),
139 139
             createPrintSection.bottomAnchor.constraint(equalTo: documentView.bottomAnchor, constant: -40),
140 140
         ])
@@ -192,7 +192,7 @@ class ViewController: NSViewController {
192 192
 
193 193
         let cardsStack = NSStackView()
194 194
         cardsStack.orientation = .horizontal
195
-        cardsStack.spacing = 20
195
+        cardsStack.spacing = AppTheme.quickStartSpacing
196 196
         cardsStack.distribution = .fillEqually
197 197
         cardsStack.translatesAutoresizingMaskIntoConstraints = false
198 198
 
@@ -258,51 +258,65 @@ class ViewController: NSViewController {
258 258
             FeatureCardData(title: "OCR File", subtitle: "Scan and print text from images", iconKind: .ocrFile),
259 259
         ]
260 260
 
261
-        let row1 = makeFeatureRow(features: Array(features[0..<4]), columns: 4)
262
-        let row2 = makeFeatureRow(features: Array(features[4..<6]), columns: 4)
263
-
264
-        let gridStack = NSStackView(views: [row1, row2])
265
-        gridStack.orientation = .vertical
266
-        gridStack.spacing = 16
267
-        gridStack.distribution = .fill
268
-        gridStack.translatesAutoresizingMaskIntoConstraints = false
269
-        section.addSubview(gridStack)
261
+        let grid = makeFeatureGrid(features: features, columns: 4)
262
+        section.addSubview(grid)
270 263
 
271 264
         NSLayoutConstraint.activate([
272 265
             title.leadingAnchor.constraint(equalTo: section.leadingAnchor),
273 266
             title.topAnchor.constraint(equalTo: section.topAnchor),
274 267
 
275
-            gridStack.leadingAnchor.constraint(equalTo: section.leadingAnchor),
276
-            gridStack.trailingAnchor.constraint(equalTo: section.trailingAnchor),
277
-            gridStack.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 16),
278
-            gridStack.bottomAnchor.constraint(equalTo: section.bottomAnchor),
268
+            grid.leadingAnchor.constraint(equalTo: section.leadingAnchor),
269
+            grid.trailingAnchor.constraint(equalTo: section.trailingAnchor),
270
+            grid.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 16),
271
+            grid.bottomAnchor.constraint(equalTo: section.bottomAnchor),
279 272
         ])
280 273
 
281 274
         return section
282 275
     }
283 276
 
284
-    private func makeFeatureRow(features: [FeatureCardData], columns: Int) -> NSStackView {
285
-        let row = NSStackView()
286
-        row.orientation = .horizontal
287
-        row.spacing = 16
288
-        row.distribution = .fillEqually
289
-        row.alignment = .centerY
290
-        row.translatesAutoresizingMaskIntoConstraints = false
277
+    private func makeFeatureGrid(features: [FeatureCardData], columns: Int) -> NSView {
278
+        let grid = NSView()
279
+        grid.translatesAutoresizingMaskIntoConstraints = false
280
+
281
+        let spacing = AppTheme.featureGridSpacing
282
+        let cards = features.map { FeatureCardView(data: $0) }
283
+        let rowCount = (cards.count + columns - 1) / columns
291 284
 
292
-        for feature in features {
293
-            row.addArrangedSubview(FeatureCardView(data: feature))
285
+        for card in cards {
286
+            grid.addSubview(card)
294 287
         }
295 288
 
296
-        if features.count < columns {
297
-            let spacerCount = columns - features.count
298
-            for _ in 0..<spacerCount {
299
-                let spacer = NSView()
300
-                spacer.translatesAutoresizingMaskIntoConstraints = false
301
-                row.addArrangedSubview(spacer)
289
+        for (index, card) in cards.enumerated() {
290
+            let row = index / columns
291
+            let col = index % columns
292
+            let columnAnchor = cards[col]
293
+
294
+            if col == 0 {
295
+                card.leadingAnchor.constraint(equalTo: grid.leadingAnchor).isActive = true
296
+            } else {
297
+                let leftCard = cards[index - 1]
298
+                card.leadingAnchor.constraint(equalTo: leftCard.trailingAnchor, constant: spacing).isActive = true
299
+            }
300
+
301
+            card.widthAnchor.constraint(equalTo: columnAnchor.widthAnchor).isActive = true
302
+
303
+            if row == 0 {
304
+                card.topAnchor.constraint(equalTo: grid.topAnchor).isActive = true
305
+            } else {
306
+                let aboveCard = cards[index - columns]
307
+                card.topAnchor.constraint(equalTo: aboveCard.bottomAnchor, constant: spacing).isActive = true
308
+            }
309
+
310
+            if col == columns - 1 {
311
+                card.trailingAnchor.constraint(equalTo: grid.trailingAnchor).isActive = true
312
+            }
313
+
314
+            if row == rowCount - 1 {
315
+                card.bottomAnchor.constraint(equalTo: grid.bottomAnchor).isActive = true
302 316
             }
303 317
         }
304 318
 
305
-        return row
319
+        return grid
306 320
     }
307 321
 }
308 322