1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @flow
    
  8.  */
    
  9. 
    
  10. import type {Rect} from './geometry';
    
  11. import type {View} from './View';
    
  12. 
    
  13. export type LayoutInfo = {view: View, frame: Rect};
    
  14. export type Layout = LayoutInfo[];
    
  15. 
    
  16. /**
    
  17.  * A function that takes a list of subviews, currently laid out in
    
  18.  * `existingLayout`, and lays them out into `containingFrame`.
    
  19.  */
    
  20. export type Layouter = (
    
  21.   existingLayout: Layout,
    
  22.   containingFrame: Rect,
    
  23. ) => Layout;
    
  24. 
    
  25. function viewToLayoutInfo(view: View): LayoutInfo {
    
  26.   return {view, frame: view.frame};
    
  27. }
    
  28. 
    
  29. export function viewsToLayout(views: View[]): Layout {
    
  30.   return views.map(viewToLayoutInfo);
    
  31. }
    
  32. 
    
  33. /**
    
  34.  * Applies `layout`'s `frame`s to its corresponding `view`.
    
  35.  */
    
  36. export function collapseLayoutIntoViews(layout: Layout) {
    
  37.   layout.forEach(({view, frame}) => view.setFrame(frame));
    
  38. }
    
  39. 
    
  40. /**
    
  41.  * A no-operation layout; does not modify the layout.
    
  42.  */
    
  43. export const noopLayout: Layouter = layout => layout;
    
  44. 
    
  45. /**
    
  46.  * Layer views on top of each other. All views' frames will be set to `containerFrame`.
    
  47.  *
    
  48.  * Equivalent to composing:
    
  49.  * - `alignToContainerXLayout`,
    
  50.  * - `alignToContainerYLayout`,
    
  51.  * - `containerWidthLayout`, and
    
  52.  * - `containerHeightLayout`.
    
  53.  */
    
  54. export const layeredLayout: Layouter = (layout, containerFrame) => {
    
  55.   return layout.map(layoutInfo => ({...layoutInfo, frame: containerFrame}));
    
  56. };
    
  57. 
    
  58. /**
    
  59.  * Stacks `views` vertically in `frame`.
    
  60.  * All views in `views` will have their widths set to the frame's width.
    
  61.  */
    
  62. export const verticallyStackedLayout: Layouter = (layout, containerFrame) => {
    
  63.   let currentY = containerFrame.origin.y;
    
  64.   return layout.map(layoutInfo => {
    
  65.     const desiredSize = layoutInfo.view.desiredSize();
    
  66.     const height = desiredSize
    
  67.       ? desiredSize.height
    
  68.       : containerFrame.origin.y + containerFrame.size.height - currentY;
    
  69.     const proposedFrame = {
    
  70.       origin: {x: containerFrame.origin.x, y: currentY},
    
  71.       size: {width: containerFrame.size.width, height},
    
  72.     };
    
  73.     currentY += height;
    
  74.     return {
    
  75.       ...layoutInfo,
    
  76.       frame: proposedFrame,
    
  77.     };
    
  78.   });
    
  79. };
    
  80. 
    
  81. /**
    
  82.  * A layouter that aligns all frames' lefts to the container frame's left.
    
  83.  */
    
  84. export const alignToContainerXLayout: Layouter = (layout, containerFrame) => {
    
  85.   return layout.map(layoutInfo => ({
    
  86.     ...layoutInfo,
    
  87.     frame: {
    
  88.       origin: {
    
  89.         x: containerFrame.origin.x,
    
  90.         y: layoutInfo.frame.origin.y,
    
  91.       },
    
  92.       size: layoutInfo.frame.size,
    
  93.     },
    
  94.   }));
    
  95. };
    
  96. 
    
  97. /**
    
  98.  * A layouter that aligns all frames' tops to the container frame's top.
    
  99.  */
    
  100. export const alignToContainerYLayout: Layouter = (layout, containerFrame) => {
    
  101.   return layout.map(layoutInfo => ({
    
  102.     ...layoutInfo,
    
  103.     frame: {
    
  104.       origin: {
    
  105.         x: layoutInfo.frame.origin.x,
    
  106.         y: containerFrame.origin.y,
    
  107.       },
    
  108.       size: layoutInfo.frame.size,
    
  109.     },
    
  110.   }));
    
  111. };
    
  112. 
    
  113. /**
    
  114.  * A layouter that sets all frames' widths to `containerFrame.size.width`.
    
  115.  */
    
  116. export const containerWidthLayout: Layouter = (layout, containerFrame) => {
    
  117.   return layout.map(layoutInfo => ({
    
  118.     ...layoutInfo,
    
  119.     frame: {
    
  120.       origin: layoutInfo.frame.origin,
    
  121.       size: {
    
  122.         width: containerFrame.size.width,
    
  123.         height: layoutInfo.frame.size.height,
    
  124.       },
    
  125.     },
    
  126.   }));
    
  127. };
    
  128. 
    
  129. /**
    
  130.  * A layouter that sets all frames' heights to `containerFrame.size.height`.
    
  131.  */
    
  132. export const containerHeightLayout: Layouter = (layout, containerFrame) => {
    
  133.   return layout.map(layoutInfo => ({
    
  134.     ...layoutInfo,
    
  135.     frame: {
    
  136.       origin: layoutInfo.frame.origin,
    
  137.       size: {
    
  138.         width: layoutInfo.frame.size.width,
    
  139.         height: containerFrame.size.height,
    
  140.       },
    
  141.     },
    
  142.   }));
    
  143. };
    
  144. 
    
  145. /**
    
  146.  * A layouter that sets all frames' heights to the desired height of its view.
    
  147.  * If the view has no desired size, the frame's height is set to 0.
    
  148.  */
    
  149. export const desiredHeightLayout: Layouter = layout => {
    
  150.   return layout.map(layoutInfo => {
    
  151.     const desiredSize = layoutInfo.view.desiredSize();
    
  152.     const height = desiredSize ? desiredSize.height : 0;
    
  153.     return {
    
  154.       ...layoutInfo,
    
  155.       frame: {
    
  156.         origin: layoutInfo.frame.origin,
    
  157.         size: {
    
  158.           width: layoutInfo.frame.size.width,
    
  159.           height,
    
  160.         },
    
  161.       },
    
  162.     };
    
  163.   });
    
  164. };
    
  165. 
    
  166. /**
    
  167.  * A layouter that sets all frames' heights to the height of the tallest frame.
    
  168.  */
    
  169. export const uniformMaxSubviewHeightLayout: Layouter = layout => {
    
  170.   const maxHeight = Math.max(
    
  171.     ...layout.map(layoutInfo => layoutInfo.frame.size.height),
    
  172.   );
    
  173.   return layout.map(layoutInfo => ({
    
  174.     ...layoutInfo,
    
  175.     frame: {
    
  176.       origin: layoutInfo.frame.origin,
    
  177.       size: {
    
  178.         width: layoutInfo.frame.size.width,
    
  179.         height: maxHeight,
    
  180.       },
    
  181.     },
    
  182.   }));
    
  183. };
    
  184. 
    
  185. /**
    
  186.  * A layouter that sets heights in this fashion:
    
  187.  * - If a frame's height >= `containerFrame.size.height`, the frame is left unchanged.
    
  188.  * - Otherwise, sets the frame's height to `containerFrame.size.height`.
    
  189.  */
    
  190. export const atLeastContainerHeightLayout: Layouter = (
    
  191.   layout,
    
  192.   containerFrame,
    
  193. ) => {
    
  194.   return layout.map(layoutInfo => ({
    
  195.     ...layoutInfo,
    
  196.     frame: {
    
  197.       origin: layoutInfo.frame.origin,
    
  198.       size: {
    
  199.         width: layoutInfo.frame.size.width,
    
  200.         height: Math.max(
    
  201.           containerFrame.size.height,
    
  202.           layoutInfo.frame.size.height,
    
  203.         ),
    
  204.       },
    
  205.     },
    
  206.   }));
    
  207. };
    
  208. 
    
  209. /**
    
  210.  * Create a layouter that applies each layouter in `layouters` in sequence.
    
  211.  */
    
  212. export function createComposedLayout(...layouters: Layouter[]): Layouter {
    
  213.   if (layouters.length === 0) {
    
  214.     return noopLayout;
    
  215.   }
    
  216. 
    
  217.   const composedLayout: Layouter = (layout, containerFrame) => {
    
  218.     return layouters.reduce(
    
  219.       (intermediateLayout, layouter) =>
    
  220.         layouter(intermediateLayout, containerFrame),
    
  221.       layout,
    
  222.     );
    
  223.   };
    
  224.   return composedLayout;
    
  225. }