View.swift 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. /// A view that can be rendered by any backend.
  2. @MainActor
  3. public protocol View {
  4. /// The view's content (composed of other views).
  5. associatedtype Content: View
  6. /// The view's contents.
  7. @ViewBuilder var body: Content { get }
  8. /// Gets the view's children as a type-erased collection of view graph
  9. /// nodes.
  10. ///
  11. /// The collection is type-erased to avoid leaking complex requirements to
  12. /// users implementing their own regular views.
  13. ///
  14. /// - Parameters:
  15. /// - backend: The app's backend.
  16. /// - snapshots: A list of snapshots, used to restore view state during a
  17. /// hot reload.
  18. /// - environment: The current environment.
  19. /// - Returns: The view's children as a type-erased collection of view graph
  20. /// nodes.
  21. func children<Backend: BaseAppBackend>(
  22. backend: Backend,
  23. snapshots: [ViewGraphSnapshotter.NodeSnapshot]?,
  24. environment: EnvironmentValues
  25. ) -> any ViewGraphNodeChildren
  26. // TODO: Perhaps this can be split off into a separate protocol for the `TupleViewN`s
  27. // if we can set up the generics right for VStack.
  28. /// Gets the view's children in a format that can be consumed by the
  29. /// ``LayoutSystem``.
  30. ///
  31. /// This really only needs to be its own method for views such as ``VStack``
  32. /// which treat their child's children as their own and skip over their
  33. /// direct child. Only needs to be implemented by the `TupleViewN`s.
  34. ///
  35. /// - Parameters:
  36. /// - backend: The app's backend.
  37. /// - children: The view's children.
  38. /// - Returns: The view's children in a format that can be consumed by the
  39. /// ``LayoutSystem``.
  40. func layoutableChildren<Backend: BaseAppBackend>(
  41. backend: Backend,
  42. children: any ViewGraphNodeChildren
  43. ) -> [LayoutSystem.LayoutableChild]
  44. /// Creates the view's widget using the supplied backend.
  45. ///
  46. /// A view is represented by the same widget instance for the whole time
  47. /// that it's visible even if its content is changing; keep that in mind
  48. /// while deciding the structure of the widget. For example, a view
  49. /// displaying one of two children should use ``BackendFeatures/GenericContainers/createContainer()``
  50. /// to create a container for the displayed child instead of just directly
  51. /// returning the widget of the currently displayed child (which would
  52. /// result in you not being able to ever switch to displaying the other
  53. /// child). This constraint significantly simplifies view implementations
  54. /// without requiring widgets to be re-created after every single update.
  55. ///
  56. /// - Parameters:
  57. /// - children: The view's children.
  58. /// - backend: The app's backend.
  59. /// - Returns: The view's widget created using the given backend.
  60. func asWidget<Backend: BaseAppBackend>(
  61. _ children: any ViewGraphNodeChildren,
  62. backend: Backend
  63. ) -> Backend.Widget
  64. /// Computes this view's layout after a state change or a change in
  65. /// available space.
  66. ///
  67. /// This method should _not_ apply the layout to `widget`; that should be
  68. /// done in ``commit(_:children:layout:environment:backend:)`` instead.
  69. ///
  70. /// `proposedSize` is the size suggested by the parent container, but child
  71. /// views always get the final call on their own size.
  72. ///
  73. /// - Parameters:
  74. /// - widget: The view's underlying widget.
  75. /// - children: The view's children.
  76. /// - proposedSize: The size suggested to the view by its parent
  77. /// container.
  78. /// - environment: The current environment.
  79. /// - backend: The app's backend.
  80. /// - Returns: The view's computed size, along with any propagated
  81. /// preferences.
  82. func computeLayout<Backend: BaseAppBackend>(
  83. _ widget: Backend.Widget,
  84. children: any ViewGraphNodeChildren,
  85. proposedSize: ProposedViewSize,
  86. environment: EnvironmentValues,
  87. backend: Backend
  88. ) -> ViewLayoutResult
  89. /// Commits the last computed layout to the underlying widget hierarchy.
  90. ///
  91. /// - Parameters:
  92. /// - widget: The view's underlying widget.
  93. /// - children: The view's children.
  94. /// - layout: The layout to use for the view. Guaranteed to be the
  95. /// last value returned by
  96. /// ``computeLayout(_:children:proposedSize:environment:backend:)``.
  97. /// - environment: The current environment.
  98. /// - backend: The app's backend.
  99. func commit<Backend: BaseAppBackend>(
  100. _ widget: Backend.Widget,
  101. children: any ViewGraphNodeChildren,
  102. layout: ViewLayoutResult,
  103. environment: EnvironmentValues,
  104. backend: Backend
  105. )
  106. /// Returns this view as an array of ``MenuItem``s.
  107. ///
  108. /// The default implementation forwards to ``body``; you should never have to override this.
  109. ///
  110. /// - Warning: This is an implementation detail and is subject to be changed or removed at any
  111. /// time.
  112. var _asMenuItems: [MenuItem] { get }
  113. }
  114. extension View {
  115. public func children<Backend: BaseAppBackend>(
  116. backend: Backend,
  117. snapshots: [ViewGraphSnapshotter.NodeSnapshot]?,
  118. environment: EnvironmentValues
  119. ) -> any ViewGraphNodeChildren {
  120. defaultChildren(
  121. backend: backend,
  122. snapshots: snapshots,
  123. environment: environment
  124. )
  125. }
  126. /// The default `View.children` implementation. Haters may see this as a
  127. /// composition lover re-implementing inheritance; I see it as innovation.
  128. public func defaultChildren<Backend: BaseAppBackend>(
  129. backend: Backend,
  130. snapshots: [ViewGraphSnapshotter.NodeSnapshot]?,
  131. environment: EnvironmentValues
  132. ) -> any ViewGraphNodeChildren {
  133. body.children(backend: backend, snapshots: snapshots, environment: environment)
  134. }
  135. public func layoutableChildren<Backend: BaseAppBackend>(
  136. backend: Backend,
  137. children: any ViewGraphNodeChildren
  138. ) -> [LayoutSystem.LayoutableChild] {
  139. defaultLayoutableChildren(backend: backend, children: children)
  140. }
  141. /// The default `View.layoutableChildren` implementation. Haters may see
  142. /// this as a composition lover re-implementing inheritance; I see it as
  143. /// innovation.
  144. public func defaultLayoutableChildren<Backend: BaseAppBackend>(
  145. backend: Backend,
  146. children: any ViewGraphNodeChildren
  147. ) -> [LayoutSystem.LayoutableChild] {
  148. body.layoutableChildren(backend: backend, children: children)
  149. }
  150. public func asWidget<Backend: BaseAppBackend>(
  151. _ children: any ViewGraphNodeChildren,
  152. backend: Backend
  153. ) -> Backend.Widget {
  154. defaultAsWidget(children, backend: backend)
  155. }
  156. /// The default `View.asWidget` implementation. Haters may see this as a
  157. /// composition lover re-implementing inheritance; I see it as innovation.
  158. public func defaultAsWidget<Backend: BaseAppBackend>(
  159. _ children: any ViewGraphNodeChildren,
  160. backend: Backend
  161. ) -> Backend.Widget {
  162. let vStack = VStack(content: body)
  163. return vStack.asWidget(children, backend: backend)
  164. }
  165. public func computeLayout<Backend: BaseAppBackend>(
  166. _ widget: Backend.Widget,
  167. children: any ViewGraphNodeChildren,
  168. proposedSize: ProposedViewSize,
  169. environment: EnvironmentValues,
  170. backend: Backend
  171. ) -> ViewLayoutResult {
  172. defaultComputeLayout(
  173. widget,
  174. children: children,
  175. proposedSize: proposedSize,
  176. environment: environment,
  177. backend: backend
  178. )
  179. }
  180. /// The default `View.computeLayout` implementation. Haters may see this as a
  181. /// composition lover re-implementing inheritance; I see it as innovation.
  182. public func defaultComputeLayout<Backend: BaseAppBackend>(
  183. _ widget: Backend.Widget,
  184. children: any ViewGraphNodeChildren,
  185. proposedSize: ProposedViewSize,
  186. environment: EnvironmentValues,
  187. backend: Backend
  188. ) -> ViewLayoutResult {
  189. let vStack = VStack(content: body)
  190. return vStack.computeLayout(
  191. widget,
  192. children: children,
  193. proposedSize: proposedSize,
  194. environment: environment,
  195. backend: backend
  196. )
  197. }
  198. public func commit<Backend: BaseAppBackend>(
  199. _ widget: Backend.Widget,
  200. children: any ViewGraphNodeChildren,
  201. layout: ViewLayoutResult,
  202. environment: EnvironmentValues,
  203. backend: Backend
  204. ) {
  205. defaultCommit(
  206. widget,
  207. children: children,
  208. layout: layout,
  209. environment: environment,
  210. backend: backend
  211. )
  212. }
  213. public func defaultCommit<Backend: BaseAppBackend>(
  214. _ widget: Backend.Widget,
  215. children: any ViewGraphNodeChildren,
  216. layout: ViewLayoutResult,
  217. environment: EnvironmentValues,
  218. backend: Backend
  219. ) {
  220. let vStack = VStack(content: body)
  221. return vStack.commit(
  222. widget,
  223. children: children,
  224. layout: layout,
  225. environment: environment,
  226. backend: backend
  227. )
  228. }
  229. public var _asMenuItems: [MenuItem] { body._asMenuItems }
  230. /// Resolves this view's menu content to the representation used by backends.
  231. ///
  232. /// This is the same resolution applied to ``Menu`` content and scene ``Commands``.
  233. /// - Returns: The resolved menu.
  234. @MainActor
  235. @_spi(Backends) public func resolvedMenuContent() -> ResolvedMenu {
  236. Menu.resolve(items: _asMenuItems)
  237. }
  238. }