Geen omschrijving

MyProfilePageView.swift 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. //
  2. // MyProfilePageView.swift
  3. // App for Indeed
  4. //
  5. // Light-theme profile editor: card layout, adaptive two-column rows, and
  6. // vertical scrolling when the window is short.
  7. //
  8. import Cocoa
  9. import UniformTypeIdentifiers
  10. private enum ProfilePagePalette {
  11. static let brandBlue = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 1)
  12. static let brandBlueHover = NSColor(srgbRed: 28 / 255, green: 70 / 255, blue: 140 / 255, alpha: 1)
  13. static let pageBackground = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
  14. static let cardBackground = NSColor(srgbRed: 252 / 255, green: 252 / 255, blue: 252 / 255, alpha: 1)
  15. static let fieldFill = NSColor(srgbRed: 247 / 255, green: 247 / 255, blue: 247 / 255, alpha: 1)
  16. static let primaryText = NSColor(srgbRed: 45 / 255, green: 45 / 255, blue: 45 / 255, alpha: 1)
  17. static let secondaryText = NSColor(srgbRed: 118 / 255, green: 118 / 255, blue: 118 / 255, alpha: 1)
  18. static let border = NSColor(srgbRed: 212 / 255, green: 210 / 255, blue: 208 / 255, alpha: 1)
  19. static let avatarWell = NSColor(srgbRed: 232 / 255, green: 232 / 255, blue: 232 / 255, alpha: 1)
  20. }
  21. final class MyProfilePageView: NSView {
  22. private static let compactFormWidth: CGFloat = 520
  23. private let scrollView = NSScrollView()
  24. private let documentView = NSView()
  25. private let cardView = NSView()
  26. private let formStack = NSStackView()
  27. private let profileNameField = NSTextField()
  28. private let fullNameField = NSTextField()
  29. private let emailField = NSTextField()
  30. private let phoneField = NSTextField()
  31. private let jobTitleField = NSTextField()
  32. private let addressField = NSTextField()
  33. private let careerField = NSTextField()
  34. private let avatarImageView = NSImageView()
  35. private let uploadPhotoButton = NSButton(title: "Upload Photo", target: nil, action: nil)
  36. private let saveButton = ProfilePrimaryButton(title: "Save Profile →", target: nil, action: nil)
  37. private let nameEmailRow = NSStackView()
  38. private let phoneJobRow = NSStackView()
  39. private var lastCompactLayout: Bool?
  40. override init(frame frameRect: NSRect) {
  41. super.init(frame: frameRect)
  42. setup()
  43. }
  44. required init?(coder: NSCoder) {
  45. super.init(coder: coder)
  46. setup()
  47. }
  48. override func layout() {
  49. super.layout()
  50. applyResponsiveRowsIfNeeded()
  51. }
  52. private func setup() {
  53. wantsLayer = true
  54. layer?.backgroundColor = ProfilePagePalette.pageBackground.cgColor
  55. scrollView.translatesAutoresizingMaskIntoConstraints = false
  56. scrollView.hasVerticalScroller = true
  57. scrollView.hasHorizontalScroller = false
  58. scrollView.autohidesScrollers = true
  59. scrollView.drawsBackground = false
  60. scrollView.borderType = .noBorder
  61. scrollView.scrollerStyle = .overlay
  62. scrollView.automaticallyAdjustsContentInsets = false
  63. documentView.translatesAutoresizingMaskIntoConstraints = false
  64. documentView.userInterfaceLayoutDirection = .leftToRight
  65. cardView.translatesAutoresizingMaskIntoConstraints = false
  66. cardView.wantsLayer = true
  67. cardView.layer?.backgroundColor = ProfilePagePalette.cardBackground.cgColor
  68. cardView.layer?.cornerRadius = 16
  69. cardView.layer?.borderWidth = 1
  70. cardView.layer?.borderColor = ProfilePagePalette.border.cgColor
  71. cardView.userInterfaceLayoutDirection = .leftToRight
  72. if #available(macOS 11.0, *) {
  73. cardView.layer?.cornerCurve = .continuous
  74. }
  75. formStack.translatesAutoresizingMaskIntoConstraints = false
  76. formStack.orientation = .vertical
  77. formStack.alignment = .width
  78. formStack.spacing = 20
  79. formStack.edgeInsets = NSEdgeInsets(top: 28, left: 28, bottom: 28, right: 28)
  80. formStack.userInterfaceLayoutDirection = .leftToRight
  81. addSubview(scrollView)
  82. scrollView.documentView = documentView
  83. documentView.addSubview(cardView)
  84. cardView.addSubview(formStack)
  85. NSLayoutConstraint.activate([
  86. scrollView.leadingAnchor.constraint(equalTo: leadingAnchor),
  87. scrollView.trailingAnchor.constraint(equalTo: trailingAnchor),
  88. scrollView.topAnchor.constraint(equalTo: topAnchor),
  89. scrollView.bottomAnchor.constraint(equalTo: bottomAnchor),
  90. documentView.leadingAnchor.constraint(equalTo: scrollView.contentView.leadingAnchor),
  91. documentView.trailingAnchor.constraint(equalTo: scrollView.contentView.trailingAnchor),
  92. documentView.topAnchor.constraint(equalTo: scrollView.contentView.topAnchor),
  93. documentView.widthAnchor.constraint(equalTo: scrollView.contentView.widthAnchor),
  94. cardView.leadingAnchor.constraint(equalTo: documentView.leadingAnchor, constant: 24),
  95. cardView.trailingAnchor.constraint(equalTo: documentView.trailingAnchor, constant: -24),
  96. cardView.topAnchor.constraint(equalTo: documentView.topAnchor, constant: 16),
  97. cardView.bottomAnchor.constraint(equalTo: documentView.bottomAnchor, constant: -24),
  98. formStack.leadingAnchor.constraint(equalTo: cardView.leadingAnchor),
  99. formStack.trailingAnchor.constraint(equalTo: cardView.trailingAnchor),
  100. formStack.topAnchor.constraint(equalTo: cardView.topAnchor),
  101. formStack.bottomAnchor.constraint(equalTo: cardView.bottomAnchor)
  102. ])
  103. formStack.addArrangedSubview(labeledGroup(title: "Profile Name *", field: profileNameField, placeholder: "Marketing Director Profile"))
  104. formStack.addArrangedSubview(sectionHeading("Personal Information"))
  105. let nameGroup = labeledGroup(title: "Full Name *", field: fullNameField, placeholder: "John Doe")
  106. let emailGroup = labeledGroup(title: "Email *", field: emailField, placeholder: "john@example.com")
  107. configureTwoColumnRow(nameEmailRow, left: nameGroup, right: emailGroup)
  108. pinEqualColumnWidths(in: nameEmailRow)
  109. formStack.addArrangedSubview(nameEmailRow)
  110. let phoneGroup = labeledGroup(title: "Phone", field: phoneField, placeholder: "+1 (555) 123-4567")
  111. let jobGroup = labeledGroup(title: "Job Title *", field: jobTitleField, placeholder: "Software Engineer")
  112. configureTwoColumnRow(phoneJobRow, left: phoneGroup, right: jobGroup)
  113. pinEqualColumnWidths(in: phoneJobRow)
  114. formStack.addArrangedSubview(phoneJobRow)
  115. formStack.addArrangedSubview(labeledGroup(title: "Address", field: addressField, placeholder: "123 Main St, City, State, ZIP"))
  116. formStack.addArrangedSubview(careerSummaryBlock())
  117. formStack.addArrangedSubview(profileImageBlock())
  118. formStack.addArrangedSubview(saveButtonHost())
  119. uploadPhotoButton.target = self
  120. uploadPhotoButton.action = #selector(didTapUploadPhoto)
  121. saveButton.target = self
  122. saveButton.action = #selector(didTapSave)
  123. }
  124. private func applyResponsiveRowsIfNeeded() {
  125. let w = cardView.bounds.width
  126. guard w > 1 else { return }
  127. let formWidth = max(0, w - formStack.edgeInsets.left - formStack.edgeInsets.right)
  128. let compact = formWidth < Self.compactFormWidth
  129. guard compact != lastCompactLayout else { return }
  130. lastCompactLayout = compact
  131. let orientation: NSUserInterfaceLayoutOrientation = compact ? .vertical : .horizontal
  132. let rowSpacing: CGFloat = compact ? 16 : 12
  133. nameEmailRow.orientation = orientation
  134. nameEmailRow.spacing = rowSpacing
  135. phoneJobRow.orientation = orientation
  136. phoneJobRow.spacing = rowSpacing
  137. nameEmailRow.distribution = compact ? .fill : .fillEqually
  138. phoneJobRow.distribution = compact ? .fill : .fillEqually
  139. }
  140. /// Keeps two columns the same width when the row is horizontal; avoids NSTextField intrinsic width fighting the stack.
  141. private func pinEqualColumnWidths(in row: NSStackView) {
  142. guard row.arrangedSubviews.count == 2 else { return }
  143. let left = row.arrangedSubviews[0]
  144. let right = row.arrangedSubviews[1]
  145. left.widthAnchor.constraint(equalTo: right.widthAnchor).isActive = true
  146. }
  147. private func configureTwoColumnRow(_ row: NSStackView, left: NSView, right: NSView) {
  148. row.translatesAutoresizingMaskIntoConstraints = false
  149. row.orientation = .horizontal
  150. row.spacing = 12
  151. row.distribution = .fillEqually
  152. row.alignment = .top
  153. row.userInterfaceLayoutDirection = .leftToRight
  154. row.setContentHuggingPriority(.defaultLow, for: .horizontal)
  155. row.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
  156. row.addArrangedSubview(left)
  157. row.addArrangedSubview(right)
  158. }
  159. private func sectionHeading(_ text: String) -> NSTextField {
  160. let label = NSTextField(labelWithString: text)
  161. label.font = .systemFont(ofSize: 15, weight: .semibold)
  162. label.textColor = ProfilePagePalette.primaryText
  163. label.alignment = .left
  164. label.translatesAutoresizingMaskIntoConstraints = false
  165. label.setContentHuggingPriority(.defaultLow, for: .horizontal)
  166. return label
  167. }
  168. private func labeledGroup(title: String, field: NSTextField, placeholder: String) -> NSView {
  169. let label = NSTextField(labelWithString: title)
  170. label.font = .systemFont(ofSize: 12, weight: .medium)
  171. label.textColor = ProfilePagePalette.secondaryText
  172. label.alignment = .left
  173. label.translatesAutoresizingMaskIntoConstraints = false
  174. styleSingleLineField(field, placeholder: placeholder)
  175. let wrap = roundedFieldChrome(containing: field, minHeight: 40)
  176. let stack = NSStackView(views: [label, wrap])
  177. stack.orientation = .vertical
  178. stack.spacing = 8
  179. stack.alignment = .width
  180. stack.translatesAutoresizingMaskIntoConstraints = false
  181. stack.userInterfaceLayoutDirection = .leftToRight
  182. stack.setContentHuggingPriority(.defaultLow, for: .horizontal)
  183. wrap.setContentHuggingPriority(.defaultLow, for: .horizontal)
  184. wrap.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
  185. return stack
  186. }
  187. private func styleSingleLineField(_ field: NSTextField, placeholder: String) {
  188. field.translatesAutoresizingMaskIntoConstraints = false
  189. field.isBordered = false
  190. field.drawsBackground = false
  191. field.focusRingType = .none
  192. field.font = .systemFont(ofSize: 14, weight: .regular)
  193. field.textColor = ProfilePagePalette.primaryText
  194. field.setContentHuggingPriority(.defaultLow, for: .horizontal)
  195. field.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
  196. field.placeholderAttributedString = NSAttributedString(
  197. string: placeholder,
  198. attributes: [
  199. .foregroundColor: ProfilePagePalette.secondaryText,
  200. .font: NSFont.systemFont(ofSize: 14, weight: .regular)
  201. ]
  202. )
  203. field.cell?.usesSingleLineMode = true
  204. field.cell?.wraps = false
  205. field.cell?.isScrollable = true
  206. }
  207. private func roundedFieldChrome(containing field: NSTextField, minHeight: CGFloat) -> NSView {
  208. let wrap = NSView()
  209. wrap.translatesAutoresizingMaskIntoConstraints = false
  210. wrap.wantsLayer = true
  211. wrap.layer?.backgroundColor = ProfilePagePalette.fieldFill.cgColor
  212. wrap.layer?.cornerRadius = 10
  213. wrap.layer?.borderWidth = 1
  214. wrap.layer?.borderColor = ProfilePagePalette.border.cgColor
  215. if #available(macOS 11.0, *) {
  216. wrap.layer?.cornerCurve = .continuous
  217. }
  218. wrap.addSubview(field)
  219. NSLayoutConstraint.activate([
  220. field.leadingAnchor.constraint(equalTo: wrap.leadingAnchor, constant: 12),
  221. field.trailingAnchor.constraint(equalTo: wrap.trailingAnchor, constant: -12),
  222. field.centerYAnchor.constraint(equalTo: wrap.centerYAnchor),
  223. wrap.heightAnchor.constraint(greaterThanOrEqualToConstant: minHeight)
  224. ])
  225. return wrap
  226. }
  227. private func careerSummaryBlock() -> NSView {
  228. let label = NSTextField(labelWithString: "Career Summary")
  229. label.font = .systemFont(ofSize: 12, weight: .medium)
  230. label.textColor = ProfilePagePalette.secondaryText
  231. label.alignment = .left
  232. label.translatesAutoresizingMaskIntoConstraints = false
  233. careerField.translatesAutoresizingMaskIntoConstraints = false
  234. careerField.isEditable = true
  235. careerField.isSelectable = true
  236. careerField.isBordered = false
  237. careerField.drawsBackground = false
  238. careerField.focusRingType = .none
  239. careerField.font = .systemFont(ofSize: 14, weight: .regular)
  240. careerField.textColor = ProfilePagePalette.primaryText
  241. careerField.maximumNumberOfLines = 0
  242. careerField.cell?.wraps = true
  243. careerField.cell?.isScrollable = false
  244. careerField.cell?.usesSingleLineMode = false
  245. careerField.stringValue = ""
  246. careerField.setContentHuggingPriority(.defaultLow, for: .horizontal)
  247. careerField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
  248. careerField.placeholderAttributedString = NSAttributedString(
  249. string: "Brief overview of your professional background and key achievements...",
  250. attributes: [
  251. .foregroundColor: ProfilePagePalette.secondaryText,
  252. .font: NSFont.systemFont(ofSize: 14, weight: .regular)
  253. ]
  254. )
  255. let wrap = NSView()
  256. wrap.translatesAutoresizingMaskIntoConstraints = false
  257. wrap.wantsLayer = true
  258. wrap.layer?.backgroundColor = ProfilePagePalette.fieldFill.cgColor
  259. wrap.layer?.cornerRadius = 10
  260. wrap.layer?.borderWidth = 1
  261. wrap.layer?.borderColor = ProfilePagePalette.border.cgColor
  262. if #available(macOS 11.0, *) {
  263. wrap.layer?.cornerCurve = .continuous
  264. }
  265. wrap.addSubview(careerField)
  266. NSLayoutConstraint.activate([
  267. careerField.leadingAnchor.constraint(equalTo: wrap.leadingAnchor, constant: 12),
  268. careerField.trailingAnchor.constraint(equalTo: wrap.trailingAnchor, constant: -12),
  269. careerField.topAnchor.constraint(equalTo: wrap.topAnchor, constant: 10),
  270. careerField.bottomAnchor.constraint(equalTo: wrap.bottomAnchor, constant: -10),
  271. wrap.heightAnchor.constraint(greaterThanOrEqualToConstant: 120)
  272. ])
  273. let stack = NSStackView(views: [label, wrap])
  274. stack.orientation = .vertical
  275. stack.spacing = 8
  276. stack.alignment = .width
  277. stack.translatesAutoresizingMaskIntoConstraints = false
  278. stack.userInterfaceLayoutDirection = .leftToRight
  279. stack.setContentHuggingPriority(.defaultLow, for: .horizontal)
  280. wrap.setContentHuggingPriority(.defaultLow, for: .horizontal)
  281. return stack
  282. }
  283. private func profileImageBlock() -> NSView {
  284. let title = NSTextField(labelWithString: "Profile Image (Optional)")
  285. title.font = .systemFont(ofSize: 12, weight: .medium)
  286. title.textColor = ProfilePagePalette.secondaryText
  287. title.translatesAutoresizingMaskIntoConstraints = false
  288. let avatarHost = NSView()
  289. avatarHost.translatesAutoresizingMaskIntoConstraints = false
  290. avatarHost.wantsLayer = true
  291. avatarHost.layer?.backgroundColor = ProfilePagePalette.avatarWell.cgColor
  292. avatarHost.layer?.cornerRadius = 36
  293. avatarHost.layer?.masksToBounds = true
  294. avatarImageView.translatesAutoresizingMaskIntoConstraints = false
  295. avatarImageView.imageScaling = .scaleProportionallyUpOrDown
  296. avatarImageView.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 36, weight: .light)
  297. avatarImageView.image = NSImage(systemSymbolName: "person.crop.circle.fill", accessibilityDescription: nil)
  298. avatarImageView.contentTintColor = ProfilePagePalette.secondaryText
  299. avatarHost.addSubview(avatarImageView)
  300. NSLayoutConstraint.activate([
  301. avatarHost.widthAnchor.constraint(equalToConstant: 72),
  302. avatarHost.heightAnchor.constraint(equalToConstant: 72),
  303. avatarImageView.centerXAnchor.constraint(equalTo: avatarHost.centerXAnchor),
  304. avatarImageView.centerYAnchor.constraint(equalTo: avatarHost.centerYAnchor),
  305. avatarImageView.widthAnchor.constraint(equalToConstant: 44),
  306. avatarImageView.heightAnchor.constraint(equalToConstant: 44)
  307. ])
  308. uploadPhotoButton.translatesAutoresizingMaskIntoConstraints = false
  309. uploadPhotoButton.bezelStyle = .rounded
  310. uploadPhotoButton.controlSize = .large
  311. uploadPhotoButton.font = .systemFont(ofSize: 13, weight: .medium)
  312. if let image = NSImage(systemSymbolName: "arrow.up.circle", accessibilityDescription: nil) {
  313. uploadPhotoButton.image = image
  314. uploadPhotoButton.imagePosition = .imageLeading
  315. uploadPhotoButton.contentTintColor = ProfilePagePalette.brandBlue
  316. }
  317. let hint = NSTextField(wrappingLabelWithString: "Recommended: Square image, max 2MB")
  318. hint.font = .systemFont(ofSize: 11, weight: .regular)
  319. hint.textColor = ProfilePagePalette.secondaryText
  320. hint.maximumNumberOfLines = 0
  321. let rightColumn = NSStackView(views: [uploadPhotoButton, hint])
  322. rightColumn.orientation = .vertical
  323. rightColumn.alignment = .leading
  324. rightColumn.spacing = 6
  325. rightColumn.translatesAutoresizingMaskIntoConstraints = false
  326. let row = NSStackView(views: [avatarHost, rightColumn])
  327. row.orientation = .horizontal
  328. row.alignment = .centerY
  329. row.spacing = 16
  330. row.translatesAutoresizingMaskIntoConstraints = false
  331. let stack = NSStackView(views: [title, row])
  332. stack.orientation = .vertical
  333. stack.spacing = 12
  334. stack.alignment = .width
  335. stack.translatesAutoresizingMaskIntoConstraints = false
  336. stack.userInterfaceLayoutDirection = .leftToRight
  337. row.setContentHuggingPriority(.defaultLow, for: .horizontal)
  338. row.alignment = .leading
  339. return stack
  340. }
  341. private func saveButtonHost() -> NSView {
  342. saveButton.translatesAutoresizingMaskIntoConstraints = false
  343. let host = NSView()
  344. host.translatesAutoresizingMaskIntoConstraints = false
  345. host.userInterfaceLayoutDirection = .leftToRight
  346. host.addSubview(saveButton)
  347. NSLayoutConstraint.activate([
  348. saveButton.leadingAnchor.constraint(equalTo: host.leadingAnchor),
  349. saveButton.trailingAnchor.constraint(equalTo: host.trailingAnchor),
  350. saveButton.topAnchor.constraint(equalTo: host.topAnchor),
  351. saveButton.bottomAnchor.constraint(equalTo: host.bottomAnchor),
  352. saveButton.heightAnchor.constraint(equalToConstant: 48)
  353. ])
  354. return host
  355. }
  356. @objc private func didTapUploadPhoto() {
  357. let panel = NSOpenPanel()
  358. panel.allowedContentTypes = [UTType.image]
  359. panel.allowsMultipleSelection = false
  360. panel.canChooseDirectories = false
  361. guard let window else { return }
  362. panel.beginSheetModal(for: window) { [weak self] response in
  363. guard response == .OK, let url = panel.url else { return }
  364. if let image = NSImage(contentsOf: url) {
  365. self?.avatarImageView.image = image
  366. self?.avatarImageView.contentTintColor = nil
  367. self?.avatarImageView.imageScaling = .scaleAxesIndependently
  368. }
  369. }
  370. }
  371. @objc private func didTapSave() {
  372. // UI shell only; wire persistence when profiles are stored.
  373. }
  374. }
  375. // MARK: - Primary CTA
  376. private final class ProfilePrimaryButton: NSButton {
  377. private var trackingArea: NSTrackingArea?
  378. private var didPushCursor = false
  379. override init(frame frameRect: NSRect) {
  380. super.init(frame: frameRect)
  381. commonInit()
  382. }
  383. required init?(coder: NSCoder) {
  384. super.init(coder: coder)
  385. commonInit()
  386. }
  387. convenience init(title: String, target: AnyObject?, action: Selector?) {
  388. self.init(frame: .zero)
  389. self.title = title
  390. self.target = target
  391. self.action = action
  392. }
  393. private func commonInit() {
  394. bezelStyle = .rounded
  395. isBordered = false
  396. font = .systemFont(ofSize: 15, weight: .semibold)
  397. contentTintColor = .white
  398. wantsLayer = true
  399. layer?.cornerRadius = 12
  400. if #available(macOS 11.0, *) {
  401. layer?.cornerCurve = .continuous
  402. }
  403. layer?.backgroundColor = ProfilePagePalette.brandBlue.cgColor
  404. }
  405. override func updateTrackingAreas() {
  406. super.updateTrackingAreas()
  407. if let trackingArea { removeTrackingArea(trackingArea) }
  408. let area = NSTrackingArea(
  409. rect: bounds,
  410. options: [.activeInKeyWindow, .mouseEnteredAndExited, .inVisibleRect],
  411. owner: self,
  412. userInfo: nil
  413. )
  414. addTrackingArea(area)
  415. trackingArea = area
  416. }
  417. override func mouseEntered(with event: NSEvent) {
  418. super.mouseEntered(with: event)
  419. layer?.backgroundColor = ProfilePagePalette.brandBlueHover.cgColor
  420. if !didPushCursor {
  421. NSCursor.pointingHand.push()
  422. didPushCursor = true
  423. }
  424. }
  425. override func mouseExited(with event: NSEvent) {
  426. super.mouseExited(with: event)
  427. layer?.backgroundColor = ProfilePagePalette.brandBlue.cgColor
  428. if didPushCursor {
  429. NSCursor.pop()
  430. didPushCursor = false
  431. }
  432. }
  433. override func viewWillMove(toWindow newWindow: NSWindow?) {
  434. super.viewWillMove(toWindow: newWindow)
  435. if newWindow == nil, didPushCursor {
  436. NSCursor.pop()
  437. didPushCursor = false
  438. }
  439. }
  440. }