Table.swift 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. /// A container that presents rows of data arranged in columns.
  2. public struct Table<RowValue, RowContent: TableRowContent<RowValue>>: TypeSafeView, View {
  3. typealias Children = TableViewChildren<RowContent.RowContent>
  4. public var body = EmptyView()
  5. /// The row data to display.
  6. private var rows: [RowValue]
  7. /// The columns to display (which each compute their cell values when given
  8. /// ``Table/Row`` instances).
  9. private var columns: RowContent
  10. /// Creates a table that computes its cell values based on a collection of
  11. /// rows.
  12. ///
  13. /// - Parameters:
  14. /// - rows: The row data to display.
  15. /// - columns: The columns to display (which each compute their cell
  16. /// values when given `Row` instances).
  17. public init(
  18. _ rows: [RowValue],
  19. @TableRowBuilder<RowValue> _ columns: () -> RowContent
  20. ) {
  21. self.rows = rows
  22. self.columns = columns()
  23. }
  24. func children<Backend: BaseAppBackend>(
  25. backend: Backend,
  26. snapshots: [ViewGraphSnapshotter.NodeSnapshot]?,
  27. environment: EnvironmentValues
  28. ) -> Children {
  29. // TODO: Table snapshotting
  30. TableViewChildren()
  31. }
  32. func asWidget<Backend: BaseAppBackend>(
  33. _ children: Children,
  34. backend: Backend
  35. ) -> Backend.Widget {
  36. func castBackend_inner<NewBackend: BaseAppBackend & BackendFeatures.Tables>(_ backend: NewBackend) -> NewBackend.Widget {
  37. return backend.createTable()
  38. }
  39. guard let castedBackend = backend as? any (BaseAppBackend & BackendFeatures.Tables) else {
  40. fatalError("Backend does not implement BackendFeatures.Tables")
  41. }
  42. return castBackend_inner(castedBackend) as! Backend.Widget
  43. }
  44. func computeLayout<Backend: BaseAppBackend>(
  45. _: Backend.Widget,
  46. children: Children,
  47. proposedSize: ProposedViewSize,
  48. environment: EnvironmentValues,
  49. backend: Backend
  50. ) -> ViewLayoutResult {
  51. func castBackend_inner<NewBackend: BaseAppBackend & BackendFeatures.Tables>(_ backend: NewBackend) -> ViewLayoutResult {
  52. let size = proposedSize
  53. var cellResults: [ViewLayoutResult] = []
  54. children.rowContent = rows.map(columns.content(for:)).map(RowView.init(_:))
  55. let columnLabels = columns.labels
  56. let columnCount = columnLabels.count
  57. // Create and destroy row nodes
  58. let remainder = children.rowContent.count - children.rowNodes.count
  59. if remainder < 0 {
  60. children.rowNodes.removeLast(-remainder)
  61. children.cellContainerWidgets.removeLast(-remainder * columnCount)
  62. } else if remainder > 0 {
  63. for row in children.rowContent[children.rowNodes.count...] {
  64. let rowNode = AnyViewGraphNode(
  65. for: row,
  66. backend: backend,
  67. environment: environment
  68. )
  69. children.rowNodes.append(rowNode)
  70. for cellWidget in rowNode.getChildren().widgets(for: backend) {
  71. let cellContainer = backend.createContainer()
  72. backend.insert(cellWidget, into: cellContainer, at: 0)
  73. children.cellContainerWidgets.append(AnyWidget(cellContainer))
  74. }
  75. }
  76. }
  77. // Update row nodes
  78. for (node, content) in zip(children.rowNodes, children.rowContent) {
  79. // TODO: Figure out if this is required
  80. // This doesn't update the row's cells. It just updates the view
  81. // instance stored in the row's ViewGraphNode
  82. _ = node.computeLayout(
  83. with: content,
  84. proposedSize: .zero,
  85. environment: environment
  86. )
  87. }
  88. // TODO: Compute a proper ideal size for tables. Look to SwiftUI to see what it does.
  89. let columnWidth = (proposedSize.width ?? 0) / Double(columnCount)
  90. // Compute cell layouts. Really only done during this initial layout
  91. // step to propagate cell preference values. Otherwise we'd do it
  92. // during commit.
  93. var rowHeights: [Int] = []
  94. let rows = zip(children.rowNodes, children.rowContent)
  95. for (rowNode, content) in rows {
  96. let rowCells = content.layoutableChildren(
  97. backend: backend,
  98. children: rowNode.getChildren()
  99. )
  100. var rowCellHeights: [Int] = []
  101. for rowCell in rowCells {
  102. let cellResult = rowCell.computeLayout(
  103. proposedSize: ProposedViewSize(
  104. columnWidth,
  105. Double(backend.defaultTableRowContentHeight)
  106. ),
  107. environment: environment
  108. )
  109. cellResults.append(cellResult)
  110. rowCellHeights.append(cellResult.size.vector.y)
  111. }
  112. let rowHeight =
  113. max(
  114. rowCellHeights.max() ?? 0,
  115. backend.defaultTableRowContentHeight
  116. ) + backend.defaultTableCellVerticalPadding * 2
  117. rowHeights.append(rowHeight)
  118. }
  119. children.rowHeights = rowHeights
  120. return ViewLayoutResult(
  121. size: size.replacingUnspecifiedDimensions(by: .zero),
  122. childResults: cellResults
  123. )
  124. }
  125. guard let castedBackend = backend as? any (BaseAppBackend & BackendFeatures.Tables) else {
  126. fatalError("Backend does not implement BackendFeatures.Tables")
  127. }
  128. return castBackend_inner(castedBackend)
  129. }
  130. func commit<Backend: BaseAppBackend>(
  131. _ widget: Backend.Widget,
  132. children: TableViewChildren<RowContent.RowContent>,
  133. layout: ViewLayoutResult,
  134. environment: EnvironmentValues,
  135. backend: Backend
  136. ) {
  137. func castBackend_inner<NewBackend: BaseAppBackend & BackendFeatures.Tables>(_ backend: NewBackend) {
  138. let widget = widget as! NewBackend.Widget
  139. let columnLabels = columns.labels
  140. backend.setRowCount(ofTable: widget, to: rows.count)
  141. backend.setColumnLabels(ofTable: widget, to: columnLabels, environment: environment)
  142. // TODO: Avoid overhead of converting `cellContainerWidgets` to
  143. // `[AnyWidget]` and back again all the time.
  144. backend.setCells(
  145. ofTable: widget,
  146. to: children.cellContainerWidgets.map { $0.into() },
  147. withRowHeights: children.rowHeights
  148. )
  149. let columnCount = columnLabels.count
  150. for (rowIndex, rowHeight) in children.rowHeights.enumerated() {
  151. let rowCells = children.rowContent[rowIndex].layoutableChildren(
  152. backend: backend,
  153. children: children.rowNodes[rowIndex].getChildren()
  154. )
  155. for (columnIndex, cell) in rowCells.enumerated() {
  156. let index = rowIndex * columnCount + columnIndex
  157. let cellSize = cell.commit()
  158. backend.setPosition(
  159. ofChildAt: 0,
  160. in: children.cellContainerWidgets[index].into(),
  161. to: SIMD2(0, (rowHeight - cellSize.size.vector.y) / 2)
  162. )
  163. }
  164. }
  165. backend.setSize(of: widget, to: layout.size.vector)
  166. }
  167. guard let castedBackend = backend as? any (BaseAppBackend & BackendFeatures.Tables) else {
  168. fatalError("Backend does not implement BackendFeatures.Tables")
  169. }
  170. castBackend_inner(castedBackend)
  171. }
  172. }
  173. class TableViewChildren<RowContent: View>: ViewGraphNodeChildren {
  174. var rowNodes: [AnyViewGraphNode<RowView<RowContent>>] = []
  175. var cellContainerWidgets: [AnyWidget] = []
  176. var rowHeights: [Int] = []
  177. var rowContent: [RowView<RowContent>] = []
  178. /// Not used, just a protocol requirement.
  179. var widgets: [AnyWidget] {
  180. rowNodes.map(\.widget)
  181. }
  182. var erasedNodes: [ErasedViewGraphNode] {
  183. rowNodes.map(ErasedViewGraphNode.init(wrapping:))
  184. }
  185. init() {
  186. rowNodes = []
  187. cellContainerWidgets = []
  188. }
  189. }
  190. /// An empty view that simply manages a row's children. Not intended to be rendered directly.
  191. struct RowView<Content: View>: View {
  192. var body: Content
  193. init(_ content: Content) {
  194. self.body = content
  195. }
  196. func layoutableChildren<Backend: BaseAppBackend>(
  197. backend: Backend,
  198. children: any ViewGraphNodeChildren
  199. ) -> [LayoutSystem.LayoutableChild] {
  200. body.layoutableChildren(backend: backend, children: children)
  201. }
  202. func children<Backend: BaseAppBackend>(
  203. backend: Backend,
  204. snapshots: [ViewGraphSnapshotter.NodeSnapshot]?,
  205. environment: EnvironmentValues
  206. ) -> any ViewGraphNodeChildren {
  207. body.children(backend: backend, snapshots: snapshots, environment: environment)
  208. }
  209. func asWidget<Backend: BaseAppBackend>(
  210. _ children: any ViewGraphNodeChildren,
  211. backend: Backend
  212. ) -> Backend.Widget {
  213. return backend.createContainer()
  214. }
  215. func computeLayout<Backend: BaseAppBackend>(
  216. _ widget: Backend.Widget,
  217. children: any ViewGraphNodeChildren,
  218. proposedSize: ProposedViewSize,
  219. environment: EnvironmentValues,
  220. backend: Backend
  221. ) -> ViewLayoutResult {
  222. return ViewLayoutResult.leafView(size: .zero)
  223. }
  224. func commit<Backend: BaseAppBackend>(
  225. _ widget: Backend.Widget,
  226. children: any ViewGraphNodeChildren,
  227. layout: ViewLayoutResult,
  228. environment: EnvironmentValues,
  229. backend: Backend
  230. ) {}
  231. }