AppLayoutDirection.swift 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. //
  2. // AppLayoutDirection.swift
  3. // App for Indeed
  4. //
  5. import Cocoa
  6. /// Layout direction for the in-app language (Arabic and Hebrew are RTL).
  7. enum AppLayoutDirection {
  8. static var isRightToLeft: Bool {
  9. currentAppLanguage().isRightToLeft
  10. }
  11. static var interface: NSUserInterfaceLayoutDirection {
  12. isRightToLeft ? .rightToLeft : .leftToRight
  13. }
  14. static var naturalTextAlignment: NSTextAlignment {
  15. isRightToLeft ? .right : .natural
  16. }
  17. static var naturalWritingDirection: NSWritingDirection {
  18. isRightToLeft ? .rightToLeft : .natural
  19. }
  20. static func naturalParagraphStyle(
  21. alignment: NSTextAlignment? = nil,
  22. lineBreakMode: NSLineBreakMode = .byWordWrapping
  23. ) -> NSParagraphStyle {
  24. let paragraph = NSMutableParagraphStyle()
  25. paragraph.alignment = alignment ?? naturalTextAlignment
  26. paragraph.baseWritingDirection = naturalWritingDirection
  27. paragraph.lineBreakMode = lineBreakMode
  28. return paragraph
  29. }
  30. static func apply(to view: NSView) {
  31. view.userInterfaceLayoutDirection = interface
  32. }
  33. static func applyRecursively(to root: NSView) {
  34. var stack: [NSView] = [root]
  35. while let view = stack.popLast() {
  36. view.userInterfaceLayoutDirection = interface
  37. stack.append(contentsOf: view.subviews)
  38. }
  39. }
  40. static func configureTextField(_ field: NSTextField) {
  41. field.alignment = naturalTextAlignment
  42. field.baseWritingDirection = naturalWritingDirection
  43. if let cell = field.cell as? NSTextFieldCell {
  44. cell.alignment = naturalTextAlignment
  45. cell.baseWritingDirection = naturalWritingDirection
  46. }
  47. }
  48. static func placeholderAttributes(
  49. font: NSFont,
  50. color: NSColor,
  51. lineBreakMode: NSLineBreakMode = .byTruncatingTail
  52. ) -> [NSAttributedString.Key: Any] {
  53. [
  54. .foregroundColor: color,
  55. .font: font,
  56. .paragraphStyle: naturalParagraphStyle(alignment: naturalTextAlignment, lineBreakMode: lineBreakMode)
  57. ]
  58. }
  59. static func attributedString(
  60. _ text: String,
  61. font: NSFont,
  62. color: NSColor,
  63. lineBreakMode: NSLineBreakMode = .byWordWrapping
  64. ) -> NSAttributedString {
  65. NSAttributedString(string: text, attributes: [
  66. .font: font,
  67. .foregroundColor: color,
  68. .paragraphStyle: naturalParagraphStyle(lineBreakMode: lineBreakMode)
  69. ])
  70. }
  71. static func refreshTextFields(in root: NSView) {
  72. var stack: [NSView] = [root]
  73. while let view = stack.popLast() {
  74. if let field = view as? NSTextField {
  75. configureTextField(field)
  76. }
  77. stack.append(contentsOf: view.subviews)
  78. }
  79. }
  80. @MainActor
  81. static func applyToAllWindows() {
  82. for window in NSApp.windows {
  83. guard let content = window.contentView else { continue }
  84. applyRecursively(to: content)
  85. refreshTextFields(in: content)
  86. }
  87. }
  88. }
  89. extension AppLanguage {
  90. var isRightToLeft: Bool {
  91. switch self {
  92. case .arabic, .hebrew:
  93. return true
  94. default:
  95. return false
  96. }
  97. }
  98. }