TextEditor.swift 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. /// A control for editing multiline text.
  2. public struct TextEditor: ElementaryView {
  3. /// The editor's content.
  4. @Binding var text: String
  5. /// Creates a text editor.
  6. ///
  7. /// - Parameter text: The editor's content.
  8. public init(text: Binding<String>) {
  9. _text = text
  10. }
  11. func asWidget<Backend: BaseAppBackend>(backend: Backend) -> Backend.Widget {
  12. backend.createTextEditor()
  13. }
  14. func computeLayout<Backend: BaseAppBackend>(
  15. _ widget: Backend.Widget,
  16. proposedSize: ProposedViewSize,
  17. environment: EnvironmentValues,
  18. backend: Backend
  19. ) -> ViewLayoutResult {
  20. // Avoid evaluating the binding multiple times
  21. let content = text
  22. let size: ViewSize
  23. if proposedSize == .unspecified {
  24. size = ViewSize(10, 10)
  25. } else if let width = proposedSize.width, proposedSize.height == nil {
  26. // See ``Text``'s computeLayout for a more details on why we clamp
  27. // the width to be positive.
  28. let idealSize = backend.size(
  29. of: content,
  30. whenDisplayedIn: widget,
  31. // For text, an infinite proposal is the same as an unspecified
  32. // proposal, and this works nicer with most backends than converting
  33. // .infinity to a large integer (which is the alternative).
  34. proposedWidth: width == .infinity ? nil : max(1, LayoutSystem.roundSize(width)),
  35. proposedHeight: nil,
  36. environment: environment
  37. )
  38. size = ViewSize(
  39. max(width, Double(idealSize.x)),
  40. Double(idealSize.y)
  41. )
  42. } else {
  43. size = proposedSize.replacingUnspecifiedDimensions(by: ViewSize(10, 10))
  44. }
  45. return ViewLayoutResult.leafView(size: size)
  46. }
  47. func commit<Backend: BaseAppBackend>(
  48. _ widget: Backend.Widget,
  49. layout: ViewLayoutResult,
  50. environment: EnvironmentValues,
  51. backend: Backend
  52. ) {
  53. // Avoid evaluating the binding multiple times
  54. let content = self.text
  55. backend.updateTextEditor(widget, environment: environment) { newValue in
  56. // We perform this check in debug mode to catch backends that cause
  57. // unnecessary binding writes, but avoid doing so in release mode
  58. // because comparing text may often be more expensive than just
  59. // avoiding the additional write at the backend level. These
  60. // additional writes are often the result of the handler being
  61. // triggered when we call backend.setContent(ofTextEditor:to:)
  62. #if DEBUG
  63. if text == newValue {
  64. logger.warning(
  65. """
  66. Unnecessary write to text Binding of TextEditor detected, \
  67. please open an issue at \(Meta.issueReportingURL) \
  68. so we can fix it for \(type(of: backend)).
  69. """
  70. )
  71. }
  72. #endif
  73. self.text = newValue
  74. }
  75. if text != backend.getContent(ofTextEditor: widget) {
  76. backend.setContent(ofTextEditor: widget, to: content)
  77. }
  78. backend.setSize(of: widget, to: layout.size.vector)
  79. }
  80. }