| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { |
| Align, |
| BoxSizing, |
| Dimension, |
| Direction, |
| Display, |
| Edge, |
| Errata, |
| ExperimentalFeature, |
| FlexDirection, |
| Gutter, |
| Justify, |
| MeasureMode, |
| Overflow, |
| PositionType, |
| Unit, |
| Wrap, |
| } from './enums.js' |
|
|
| export { |
| Align, |
| BoxSizing, |
| Dimension, |
| Direction, |
| Display, |
| Edge, |
| Errata, |
| ExperimentalFeature, |
| FlexDirection, |
| Gutter, |
| Justify, |
| MeasureMode, |
| Overflow, |
| PositionType, |
| Unit, |
| Wrap, |
| } |
|
|
| |
| |
|
|
| export type Value = { |
| unit: Unit |
| value: number |
| } |
|
|
| const UNDEFINED_VALUE: Value = { unit: Unit.Undefined, value: NaN } |
| const AUTO_VALUE: Value = { unit: Unit.Auto, value: NaN } |
|
|
| function pointValue(v: number): Value { |
| return { unit: Unit.Point, value: v } |
| } |
| function percentValue(v: number): Value { |
| return { unit: Unit.Percent, value: v } |
| } |
|
|
| function resolveValue(v: Value, ownerSize: number): number { |
| switch (v.unit) { |
| case Unit.Point: |
| return v.value |
| case Unit.Percent: |
| return isNaN(ownerSize) ? NaN : (v.value * ownerSize) / 100 |
| default: |
| return NaN |
| } |
| } |
|
|
| function isDefined(n: number): boolean { |
| return !isNaN(n) |
| } |
|
|
| |
| function sameFloat(a: number, b: number): boolean { |
| return a === b || (a !== a && b !== b) |
| } |
|
|
| |
| |
|
|
| type Layout = { |
| left: number |
| top: number |
| width: number |
| height: number |
| |
| border: [number, number, number, number] |
| padding: [number, number, number, number] |
| margin: [number, number, number, number] |
| } |
|
|
| |
| |
|
|
| type Style = { |
| direction: Direction |
| flexDirection: FlexDirection |
| justifyContent: Justify |
| alignItems: Align |
| alignSelf: Align |
| alignContent: Align |
| flexWrap: Wrap |
| overflow: Overflow |
| display: Display |
| positionType: PositionType |
|
|
| flexGrow: number |
| flexShrink: number |
| flexBasis: Value |
|
|
| |
| margin: Value[] |
| padding: Value[] |
| border: Value[] |
| position: Value[] |
|
|
| |
| gap: Value[] |
|
|
| width: Value |
| height: Value |
| minWidth: Value |
| minHeight: Value |
| maxWidth: Value |
| maxHeight: Value |
| } |
|
|
| function defaultStyle(): Style { |
| return { |
| direction: Direction.Inherit, |
| flexDirection: FlexDirection.Column, |
| justifyContent: Justify.FlexStart, |
| alignItems: Align.Stretch, |
| alignSelf: Align.Auto, |
| alignContent: Align.FlexStart, |
| flexWrap: Wrap.NoWrap, |
| overflow: Overflow.Visible, |
| display: Display.Flex, |
| positionType: PositionType.Relative, |
| flexGrow: 0, |
| flexShrink: 0, |
| flexBasis: AUTO_VALUE, |
| margin: new Array(9).fill(UNDEFINED_VALUE), |
| padding: new Array(9).fill(UNDEFINED_VALUE), |
| border: new Array(9).fill(UNDEFINED_VALUE), |
| position: new Array(9).fill(UNDEFINED_VALUE), |
| gap: new Array(3).fill(UNDEFINED_VALUE), |
| width: AUTO_VALUE, |
| height: AUTO_VALUE, |
| minWidth: UNDEFINED_VALUE, |
| minHeight: UNDEFINED_VALUE, |
| maxWidth: UNDEFINED_VALUE, |
| maxHeight: UNDEFINED_VALUE, |
| } |
| } |
|
|
| |
| |
|
|
| const EDGE_LEFT = 0 |
| const EDGE_TOP = 1 |
| const EDGE_RIGHT = 2 |
| const EDGE_BOTTOM = 3 |
|
|
| function resolveEdge( |
| edges: Value[], |
| physicalEdge: number, |
| ownerSize: number, |
| |
| allowAuto = false, |
| ): number { |
| |
| let v = edges[physicalEdge]! |
| if (v.unit === Unit.Undefined) { |
| if (physicalEdge === EDGE_LEFT || physicalEdge === EDGE_RIGHT) { |
| v = edges[Edge.Horizontal]! |
| } else { |
| v = edges[Edge.Vertical]! |
| } |
| } |
| if (v.unit === Unit.Undefined) { |
| v = edges[Edge.All]! |
| } |
| |
| if (v.unit === Unit.Undefined) { |
| if (physicalEdge === EDGE_LEFT) v = edges[Edge.Start]! |
| if (physicalEdge === EDGE_RIGHT) v = edges[Edge.End]! |
| } |
| if (v.unit === Unit.Undefined) return 0 |
| if (v.unit === Unit.Auto) return allowAuto ? NaN : 0 |
| return resolveValue(v, ownerSize) |
| } |
|
|
| function resolveEdgeRaw(edges: Value[], physicalEdge: number): Value { |
| let v = edges[physicalEdge]! |
| if (v.unit === Unit.Undefined) { |
| if (physicalEdge === EDGE_LEFT || physicalEdge === EDGE_RIGHT) { |
| v = edges[Edge.Horizontal]! |
| } else { |
| v = edges[Edge.Vertical]! |
| } |
| } |
| if (v.unit === Unit.Undefined) v = edges[Edge.All]! |
| if (v.unit === Unit.Undefined) { |
| if (physicalEdge === EDGE_LEFT) v = edges[Edge.Start]! |
| if (physicalEdge === EDGE_RIGHT) v = edges[Edge.End]! |
| } |
| return v |
| } |
|
|
| function isMarginAuto(edges: Value[], physicalEdge: number): boolean { |
| return resolveEdgeRaw(edges, physicalEdge).unit === Unit.Auto |
| } |
|
|
| |
| |
| function hasAnyAutoEdge(edges: Value[]): boolean { |
| for (let i = 0; i < 9; i++) if (edges[i]!.unit === 3) return true |
| return false |
| } |
| function hasAnyDefinedEdge(edges: Value[]): boolean { |
| for (let i = 0; i < 9; i++) if (edges[i]!.unit !== 0) return true |
| return false |
| } |
|
|
| |
| |
| |
| |
| function resolveEdges4Into( |
| edges: Value[], |
| ownerSize: number, |
| out: [number, number, number, number], |
| ): void { |
| |
| const eH = edges[6]! |
| const eV = edges[7]! |
| const eA = edges[8]! |
| const eS = edges[4]! |
| const eE = edges[5]! |
| const pctDenom = isNaN(ownerSize) ? NaN : ownerSize / 100 |
|
|
| |
| let v = edges[0]! |
| if (v.unit === 0) v = eH |
| if (v.unit === 0) v = eA |
| if (v.unit === 0) v = eS |
| out[0] = v.unit === 1 ? v.value : v.unit === 2 ? v.value * pctDenom : 0 |
|
|
| |
| v = edges[1]! |
| if (v.unit === 0) v = eV |
| if (v.unit === 0) v = eA |
| out[1] = v.unit === 1 ? v.value : v.unit === 2 ? v.value * pctDenom : 0 |
|
|
| |
| v = edges[2]! |
| if (v.unit === 0) v = eH |
| if (v.unit === 0) v = eA |
| if (v.unit === 0) v = eE |
| out[2] = v.unit === 1 ? v.value : v.unit === 2 ? v.value * pctDenom : 0 |
|
|
| |
| v = edges[3]! |
| if (v.unit === 0) v = eV |
| if (v.unit === 0) v = eA |
| out[3] = v.unit === 1 ? v.value : v.unit === 2 ? v.value * pctDenom : 0 |
| } |
|
|
| |
| |
|
|
| function isRow(dir: FlexDirection): boolean { |
| return dir === FlexDirection.Row || dir === FlexDirection.RowReverse |
| } |
| function isReverse(dir: FlexDirection): boolean { |
| return dir === FlexDirection.RowReverse || dir === FlexDirection.ColumnReverse |
| } |
| function crossAxis(dir: FlexDirection): FlexDirection { |
| return isRow(dir) ? FlexDirection.Column : FlexDirection.Row |
| } |
| function leadingEdge(dir: FlexDirection): number { |
| switch (dir) { |
| case FlexDirection.Row: |
| return EDGE_LEFT |
| case FlexDirection.RowReverse: |
| return EDGE_RIGHT |
| case FlexDirection.Column: |
| return EDGE_TOP |
| case FlexDirection.ColumnReverse: |
| return EDGE_BOTTOM |
| } |
| } |
| function trailingEdge(dir: FlexDirection): number { |
| switch (dir) { |
| case FlexDirection.Row: |
| return EDGE_RIGHT |
| case FlexDirection.RowReverse: |
| return EDGE_LEFT |
| case FlexDirection.Column: |
| return EDGE_BOTTOM |
| case FlexDirection.ColumnReverse: |
| return EDGE_TOP |
| } |
| } |
|
|
| |
| |
|
|
| export type MeasureFunction = ( |
| width: number, |
| widthMode: MeasureMode, |
| height: number, |
| heightMode: MeasureMode, |
| ) => { width: number; height: number } |
|
|
| export type Size = { width: number; height: number } |
|
|
| |
| |
|
|
| export type Config = { |
| pointScaleFactor: number |
| errata: Errata |
| useWebDefaults: boolean |
| free(): void |
| isExperimentalFeatureEnabled(_: ExperimentalFeature): boolean |
| setExperimentalFeatureEnabled(_: ExperimentalFeature, __: boolean): void |
| setPointScaleFactor(factor: number): void |
| getErrata(): Errata |
| setErrata(errata: Errata): void |
| setUseWebDefaults(v: boolean): void |
| } |
|
|
| function createConfig(): Config { |
| const config: Config = { |
| pointScaleFactor: 1, |
| errata: Errata.None, |
| useWebDefaults: false, |
| free() {}, |
| isExperimentalFeatureEnabled() { |
| return false |
| }, |
| setExperimentalFeatureEnabled() {}, |
| setPointScaleFactor(f) { |
| config.pointScaleFactor = f |
| }, |
| getErrata() { |
| return config.errata |
| }, |
| setErrata(e) { |
| config.errata = e |
| }, |
| setUseWebDefaults(v) { |
| config.useWebDefaults = v |
| }, |
| } |
| return config |
| } |
|
|
| |
| |
|
|
| export class Node { |
| style: Style |
| layout: Layout |
| parent: Node | null |
| children: Node[] |
| measureFunc: MeasureFunction | null |
| config: Config |
| isDirty_: boolean |
| isReferenceBaseline_: boolean |
|
|
| |
| _flexBasis = 0 |
| _mainSize = 0 |
| _crossSize = 0 |
| _lineIndex = 0 |
| |
| |
| |
| |
| |
| |
| _hasAutoMargin = false |
| _hasPosition = false |
| |
| |
| |
| |
| |
| _hasPadding = false |
| _hasBorder = false |
| _hasMargin = false |
| |
| |
| |
| |
| |
| |
| |
| |
| _lW = NaN |
| _lH = NaN |
| _lWM: MeasureMode = 0 |
| _lHM: MeasureMode = 0 |
| _lOW = NaN |
| _lOH = NaN |
| _lFW = false |
| _lFH = false |
| |
| |
| |
| |
| |
| _lOutW = NaN |
| _lOutH = NaN |
| _hasL = false |
| _mW = NaN |
| _mH = NaN |
| _mWM: MeasureMode = 0 |
| _mHM: MeasureMode = 0 |
| _mOW = NaN |
| _mOH = NaN |
| _mOutW = NaN |
| _mOutH = NaN |
| _hasM = false |
| |
| |
| |
| |
| |
| |
| _fbBasis = NaN |
| _fbOwnerW = NaN |
| _fbOwnerH = NaN |
| _fbAvailMain = NaN |
| _fbAvailCross = NaN |
| _fbCrossMode: MeasureMode = 0 |
| |
| |
| |
| |
| |
| |
| |
| _fbGen = -1 |
| |
| |
| |
| |
| |
| _cIn: Float64Array | null = null |
| _cOut: Float64Array | null = null |
| _cGen = -1 |
| _cN = 0 |
| _cWr = 0 |
|
|
| constructor(config?: Config) { |
| this.style = defaultStyle() |
| this.layout = { |
| left: 0, |
| top: 0, |
| width: 0, |
| height: 0, |
| border: [0, 0, 0, 0], |
| padding: [0, 0, 0, 0], |
| margin: [0, 0, 0, 0], |
| } |
| this.parent = null |
| this.children = [] |
| this.measureFunc = null |
| this.config = config ?? DEFAULT_CONFIG |
| this.isDirty_ = true |
| this.isReferenceBaseline_ = false |
| _yogaLiveNodes++ |
| } |
|
|
| |
|
|
| insertChild(child: Node, index: number): void { |
| child.parent = this |
| this.children.splice(index, 0, child) |
| this.markDirty() |
| } |
| removeChild(child: Node): void { |
| const idx = this.children.indexOf(child) |
| if (idx >= 0) { |
| this.children.splice(idx, 1) |
| child.parent = null |
| this.markDirty() |
| } |
| } |
| getChild(index: number): Node { |
| return this.children[index]! |
| } |
| getChildCount(): number { |
| return this.children.length |
| } |
| getParent(): Node | null { |
| return this.parent |
| } |
|
|
| |
|
|
| free(): void { |
| this.parent = null |
| this.children = [] |
| this.measureFunc = null |
| this._cIn = null |
| this._cOut = null |
| _yogaLiveNodes-- |
| } |
| freeRecursive(): void { |
| for (const c of this.children) c.freeRecursive() |
| this.free() |
| } |
| reset(): void { |
| this.style = defaultStyle() |
| this.children = [] |
| this.parent = null |
| this.measureFunc = null |
| this.isDirty_ = true |
| this._hasAutoMargin = false |
| this._hasPosition = false |
| this._hasPadding = false |
| this._hasBorder = false |
| this._hasMargin = false |
| this._hasL = false |
| this._hasM = false |
| this._cN = 0 |
| this._cWr = 0 |
| this._fbBasis = NaN |
| } |
|
|
| |
|
|
| markDirty(): void { |
| this.isDirty_ = true |
| if (this.parent && !this.parent.isDirty_) this.parent.markDirty() |
| } |
| isDirty(): boolean { |
| return this.isDirty_ |
| } |
| hasNewLayout(): boolean { |
| return true |
| } |
| markLayoutSeen(): void {} |
|
|
| |
|
|
| setMeasureFunc(fn: MeasureFunction | null): void { |
| this.measureFunc = fn |
| this.markDirty() |
| } |
| unsetMeasureFunc(): void { |
| this.measureFunc = null |
| this.markDirty() |
| } |
|
|
| |
|
|
| getComputedLeft(): number { |
| return this.layout.left |
| } |
| getComputedTop(): number { |
| return this.layout.top |
| } |
| getComputedWidth(): number { |
| return this.layout.width |
| } |
| getComputedHeight(): number { |
| return this.layout.height |
| } |
| getComputedRight(): number { |
| const p = this.parent |
| return p ? p.layout.width - this.layout.left - this.layout.width : 0 |
| } |
| getComputedBottom(): number { |
| const p = this.parent |
| return p ? p.layout.height - this.layout.top - this.layout.height : 0 |
| } |
| getComputedLayout(): { |
| left: number |
| top: number |
| right: number |
| bottom: number |
| width: number |
| height: number |
| } { |
| return { |
| left: this.layout.left, |
| top: this.layout.top, |
| right: this.getComputedRight(), |
| bottom: this.getComputedBottom(), |
| width: this.layout.width, |
| height: this.layout.height, |
| } |
| } |
| getComputedBorder(edge: Edge): number { |
| return this.layout.border[physicalEdge(edge)]! |
| } |
| getComputedPadding(edge: Edge): number { |
| return this.layout.padding[physicalEdge(edge)]! |
| } |
| getComputedMargin(edge: Edge): number { |
| return this.layout.margin[physicalEdge(edge)]! |
| } |
|
|
| |
|
|
| setWidth(v: number | 'auto' | string | undefined): void { |
| this.style.width = parseDimension(v) |
| this.markDirty() |
| } |
| setWidthPercent(v: number): void { |
| this.style.width = percentValue(v) |
| this.markDirty() |
| } |
| setWidthAuto(): void { |
| this.style.width = AUTO_VALUE |
| this.markDirty() |
| } |
| setHeight(v: number | 'auto' | string | undefined): void { |
| this.style.height = parseDimension(v) |
| this.markDirty() |
| } |
| setHeightPercent(v: number): void { |
| this.style.height = percentValue(v) |
| this.markDirty() |
| } |
| setHeightAuto(): void { |
| this.style.height = AUTO_VALUE |
| this.markDirty() |
| } |
| setMinWidth(v: number | string | undefined): void { |
| this.style.minWidth = parseDimension(v) |
| this.markDirty() |
| } |
| setMinWidthPercent(v: number): void { |
| this.style.minWidth = percentValue(v) |
| this.markDirty() |
| } |
| setMinHeight(v: number | string | undefined): void { |
| this.style.minHeight = parseDimension(v) |
| this.markDirty() |
| } |
| setMinHeightPercent(v: number): void { |
| this.style.minHeight = percentValue(v) |
| this.markDirty() |
| } |
| setMaxWidth(v: number | string | undefined): void { |
| this.style.maxWidth = parseDimension(v) |
| this.markDirty() |
| } |
| setMaxWidthPercent(v: number): void { |
| this.style.maxWidth = percentValue(v) |
| this.markDirty() |
| } |
| setMaxHeight(v: number | string | undefined): void { |
| this.style.maxHeight = parseDimension(v) |
| this.markDirty() |
| } |
| setMaxHeightPercent(v: number): void { |
| this.style.maxHeight = percentValue(v) |
| this.markDirty() |
| } |
|
|
| |
|
|
| setFlexDirection(dir: FlexDirection): void { |
| this.style.flexDirection = dir |
| this.markDirty() |
| } |
| setFlexGrow(v: number | undefined): void { |
| this.style.flexGrow = v ?? 0 |
| this.markDirty() |
| } |
| setFlexShrink(v: number | undefined): void { |
| this.style.flexShrink = v ?? 0 |
| this.markDirty() |
| } |
| setFlex(v: number | undefined): void { |
| if (v === undefined || isNaN(v)) { |
| this.style.flexGrow = 0 |
| this.style.flexShrink = 0 |
| } else if (v > 0) { |
| this.style.flexGrow = v |
| this.style.flexShrink = 1 |
| this.style.flexBasis = pointValue(0) |
| } else if (v < 0) { |
| this.style.flexGrow = 0 |
| this.style.flexShrink = -v |
| } else { |
| this.style.flexGrow = 0 |
| this.style.flexShrink = 0 |
| } |
| this.markDirty() |
| } |
| setFlexBasis(v: number | 'auto' | string | undefined): void { |
| this.style.flexBasis = parseDimension(v) |
| this.markDirty() |
| } |
| setFlexBasisPercent(v: number): void { |
| this.style.flexBasis = percentValue(v) |
| this.markDirty() |
| } |
| setFlexBasisAuto(): void { |
| this.style.flexBasis = AUTO_VALUE |
| this.markDirty() |
| } |
| setFlexWrap(wrap: Wrap): void { |
| this.style.flexWrap = wrap |
| this.markDirty() |
| } |
|
|
| |
|
|
| setAlignItems(a: Align): void { |
| this.style.alignItems = a |
| this.markDirty() |
| } |
| setAlignSelf(a: Align): void { |
| this.style.alignSelf = a |
| this.markDirty() |
| } |
| setAlignContent(a: Align): void { |
| this.style.alignContent = a |
| this.markDirty() |
| } |
| setJustifyContent(j: Justify): void { |
| this.style.justifyContent = j |
| this.markDirty() |
| } |
|
|
| |
|
|
| setDisplay(d: Display): void { |
| this.style.display = d |
| this.markDirty() |
| } |
| getDisplay(): Display { |
| return this.style.display |
| } |
| setPositionType(t: PositionType): void { |
| this.style.positionType = t |
| this.markDirty() |
| } |
| setPosition(edge: Edge, v: number | string | undefined): void { |
| this.style.position[edge] = parseDimension(v) |
| this._hasPosition = hasAnyDefinedEdge(this.style.position) |
| this.markDirty() |
| } |
| setPositionPercent(edge: Edge, v: number): void { |
| this.style.position[edge] = percentValue(v) |
| this._hasPosition = true |
| this.markDirty() |
| } |
| setPositionAuto(edge: Edge): void { |
| this.style.position[edge] = AUTO_VALUE |
| this._hasPosition = true |
| this.markDirty() |
| } |
| setOverflow(o: Overflow): void { |
| this.style.overflow = o |
| this.markDirty() |
| } |
| setDirection(d: Direction): void { |
| this.style.direction = d |
| this.markDirty() |
| } |
| setBoxSizing(_: BoxSizing): void { |
| |
| } |
|
|
| |
|
|
| setMargin(edge: Edge, v: number | 'auto' | string | undefined): void { |
| const val = parseDimension(v) |
| this.style.margin[edge] = val |
| if (val.unit === Unit.Auto) this._hasAutoMargin = true |
| else this._hasAutoMargin = hasAnyAutoEdge(this.style.margin) |
| this._hasMargin = |
| this._hasAutoMargin || hasAnyDefinedEdge(this.style.margin) |
| this.markDirty() |
| } |
| setMarginPercent(edge: Edge, v: number): void { |
| this.style.margin[edge] = percentValue(v) |
| this._hasAutoMargin = hasAnyAutoEdge(this.style.margin) |
| this._hasMargin = true |
| this.markDirty() |
| } |
| setMarginAuto(edge: Edge): void { |
| this.style.margin[edge] = AUTO_VALUE |
| this._hasAutoMargin = true |
| this._hasMargin = true |
| this.markDirty() |
| } |
| setPadding(edge: Edge, v: number | string | undefined): void { |
| this.style.padding[edge] = parseDimension(v) |
| this._hasPadding = hasAnyDefinedEdge(this.style.padding) |
| this.markDirty() |
| } |
| setPaddingPercent(edge: Edge, v: number): void { |
| this.style.padding[edge] = percentValue(v) |
| this._hasPadding = true |
| this.markDirty() |
| } |
| setBorder(edge: Edge, v: number | undefined): void { |
| this.style.border[edge] = v === undefined ? UNDEFINED_VALUE : pointValue(v) |
| this._hasBorder = hasAnyDefinedEdge(this.style.border) |
| this.markDirty() |
| } |
| setGap(gutter: Gutter, v: number | string | undefined): void { |
| this.style.gap[gutter] = parseDimension(v) |
| this.markDirty() |
| } |
| setGapPercent(gutter: Gutter, v: number): void { |
| this.style.gap[gutter] = percentValue(v) |
| this.markDirty() |
| } |
|
|
| |
|
|
| getFlexDirection(): FlexDirection { |
| return this.style.flexDirection |
| } |
| getJustifyContent(): Justify { |
| return this.style.justifyContent |
| } |
| getAlignItems(): Align { |
| return this.style.alignItems |
| } |
| getAlignSelf(): Align { |
| return this.style.alignSelf |
| } |
| getAlignContent(): Align { |
| return this.style.alignContent |
| } |
| getFlexGrow(): number { |
| return this.style.flexGrow |
| } |
| getFlexShrink(): number { |
| return this.style.flexShrink |
| } |
| getFlexBasis(): Value { |
| return this.style.flexBasis |
| } |
| getFlexWrap(): Wrap { |
| return this.style.flexWrap |
| } |
| getWidth(): Value { |
| return this.style.width |
| } |
| getHeight(): Value { |
| return this.style.height |
| } |
| getOverflow(): Overflow { |
| return this.style.overflow |
| } |
| getPositionType(): PositionType { |
| return this.style.positionType |
| } |
| getDirection(): Direction { |
| return this.style.direction |
| } |
|
|
| |
|
|
| copyStyle(_: Node): void {} |
| setDirtiedFunc(_: unknown): void {} |
| unsetDirtiedFunc(): void {} |
| setIsReferenceBaseline(v: boolean): void { |
| this.isReferenceBaseline_ = v |
| this.markDirty() |
| } |
| isReferenceBaseline(): boolean { |
| return this.isReferenceBaseline_ |
| } |
| setAspectRatio(_: number | undefined): void {} |
| getAspectRatio(): number { |
| return NaN |
| } |
| setAlwaysFormsContainingBlock(_: boolean): void {} |
|
|
| |
|
|
| calculateLayout( |
| ownerWidth: number | undefined, |
| ownerHeight: number | undefined, |
| _direction?: Direction, |
| ): void { |
| _yogaNodesVisited = 0 |
| _yogaMeasureCalls = 0 |
| _yogaCacheHits = 0 |
| _generation++ |
| const w = ownerWidth === undefined ? NaN : ownerWidth |
| const h = ownerHeight === undefined ? NaN : ownerHeight |
| layoutNode( |
| this, |
| w, |
| h, |
| isDefined(w) ? MeasureMode.Exactly : MeasureMode.Undefined, |
| isDefined(h) ? MeasureMode.Exactly : MeasureMode.Undefined, |
| w, |
| h, |
| true, |
| ) |
| |
| |
| |
| const mar = this.layout.margin |
| const posL = resolveValue( |
| resolveEdgeRaw(this.style.position, EDGE_LEFT), |
| isDefined(w) ? w : 0, |
| ) |
| const posT = resolveValue( |
| resolveEdgeRaw(this.style.position, EDGE_TOP), |
| isDefined(w) ? w : 0, |
| ) |
| this.layout.left = mar[EDGE_LEFT] + (isDefined(posL) ? posL : 0) |
| this.layout.top = mar[EDGE_TOP] + (isDefined(posT) ? posT : 0) |
| roundLayout(this, this.config.pointScaleFactor, 0, 0) |
| } |
| } |
|
|
| const DEFAULT_CONFIG = createConfig() |
|
|
| const CACHE_SLOTS = 4 |
| function cacheWrite( |
| node: Node, |
| aW: number, |
| aH: number, |
| wM: MeasureMode, |
| hM: MeasureMode, |
| oW: number, |
| oH: number, |
| fW: boolean, |
| fH: boolean, |
| wasDirty: boolean, |
| ): void { |
| if (!node._cIn) { |
| node._cIn = new Float64Array(CACHE_SLOTS * 8) |
| node._cOut = new Float64Array(CACHE_SLOTS * 2) |
| } |
| |
| |
| |
| |
| |
| |
| if (wasDirty && node._cGen !== _generation) { |
| node._cN = 0 |
| node._cWr = 0 |
| } |
| |
| |
| const i = node._cWr++ % CACHE_SLOTS |
| if (node._cN < CACHE_SLOTS) node._cN = node._cWr |
| const o = i * 8 |
| const cIn = node._cIn |
| cIn[o] = aW |
| cIn[o + 1] = aH |
| cIn[o + 2] = wM |
| cIn[o + 3] = hM |
| cIn[o + 4] = oW |
| cIn[o + 5] = oH |
| cIn[o + 6] = fW ? 1 : 0 |
| cIn[o + 7] = fH ? 1 : 0 |
| node._cOut![i * 2] = node.layout.width |
| node._cOut![i * 2 + 1] = node.layout.height |
| node._cGen = _generation |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| function commitCacheOutputs(node: Node, performLayout: boolean): void { |
| if (performLayout) { |
| node._lOutW = node.layout.width |
| node._lOutH = node.layout.height |
| } else { |
| node._mOutW = node.layout.width |
| node._mOutH = node.layout.height |
| } |
| } |
|
|
| |
| |
|
|
| |
| |
| |
| |
| let _generation = 0 |
| let _yogaNodesVisited = 0 |
| let _yogaMeasureCalls = 0 |
| let _yogaCacheHits = 0 |
| let _yogaLiveNodes = 0 |
| export function getYogaCounters(): { |
| visited: number |
| measured: number |
| cacheHits: number |
| live: number |
| } { |
| return { |
| visited: _yogaNodesVisited, |
| measured: _yogaMeasureCalls, |
| cacheHits: _yogaCacheHits, |
| live: _yogaLiveNodes, |
| } |
| } |
|
|
| function layoutNode( |
| node: Node, |
| availableWidth: number, |
| availableHeight: number, |
| widthMode: MeasureMode, |
| heightMode: MeasureMode, |
| ownerWidth: number, |
| ownerHeight: number, |
| performLayout: boolean, |
| |
| |
| forceWidth = false, |
| forceHeight = false, |
| ): void { |
| _yogaNodesVisited++ |
| const style = node.style |
| const layout = node.layout |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const sameGen = node._cGen === _generation && !performLayout |
| if (!node.isDirty_ || sameGen) { |
| if ( |
| !node.isDirty_ && |
| node._hasL && |
| node._lWM === widthMode && |
| node._lHM === heightMode && |
| node._lFW === forceWidth && |
| node._lFH === forceHeight && |
| sameFloat(node._lW, availableWidth) && |
| sameFloat(node._lH, availableHeight) && |
| sameFloat(node._lOW, ownerWidth) && |
| sameFloat(node._lOH, ownerHeight) |
| ) { |
| _yogaCacheHits++ |
| layout.width = node._lOutW |
| layout.height = node._lOutH |
| return |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if (node._cN > 0 && (sameGen || !node.isDirty_)) { |
| const cIn = node._cIn! |
| for (let i = 0; i < node._cN; i++) { |
| const o = i * 8 |
| if ( |
| cIn[o + 2] === widthMode && |
| cIn[o + 3] === heightMode && |
| cIn[o + 6] === (forceWidth ? 1 : 0) && |
| cIn[o + 7] === (forceHeight ? 1 : 0) && |
| sameFloat(cIn[o]!, availableWidth) && |
| sameFloat(cIn[o + 1]!, availableHeight) && |
| sameFloat(cIn[o + 4]!, ownerWidth) && |
| sameFloat(cIn[o + 5]!, ownerHeight) |
| ) { |
| layout.width = node._cOut![i * 2]! |
| layout.height = node._cOut![i * 2 + 1]! |
| _yogaCacheHits++ |
| return |
| } |
| } |
| } |
| if ( |
| !node.isDirty_ && |
| !performLayout && |
| node._hasM && |
| node._mWM === widthMode && |
| node._mHM === heightMode && |
| sameFloat(node._mW, availableWidth) && |
| sameFloat(node._mH, availableHeight) && |
| sameFloat(node._mOW, ownerWidth) && |
| sameFloat(node._mOH, ownerHeight) |
| ) { |
| layout.width = node._mOutW |
| layout.height = node._mOutH |
| _yogaCacheHits++ |
| return |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| const wasDirty = node.isDirty_ |
| if (performLayout) { |
| node._lW = availableWidth |
| node._lH = availableHeight |
| node._lWM = widthMode |
| node._lHM = heightMode |
| node._lOW = ownerWidth |
| node._lOH = ownerHeight |
| node._lFW = forceWidth |
| node._lFH = forceHeight |
| node._hasL = true |
| node.isDirty_ = false |
| |
| |
| |
| |
| |
| |
| if (wasDirty) node._hasM = false |
| } else { |
| node._mW = availableWidth |
| node._mH = availableHeight |
| node._mWM = widthMode |
| node._mHM = heightMode |
| node._mOW = ownerWidth |
| node._mOH = ownerHeight |
| node._hasM = true |
| |
| |
| |
| |
| |
| |
| if (wasDirty) node._hasL = false |
| } |
|
|
| |
| |
| |
| |
| |
| const pad = layout.padding |
| const bor = layout.border |
| const mar = layout.margin |
| if (node._hasPadding) resolveEdges4Into(style.padding, ownerWidth, pad) |
| else pad[0] = pad[1] = pad[2] = pad[3] = 0 |
| if (node._hasBorder) resolveEdges4Into(style.border, ownerWidth, bor) |
| else bor[0] = bor[1] = bor[2] = bor[3] = 0 |
| if (node._hasMargin) resolveEdges4Into(style.margin, ownerWidth, mar) |
| else mar[0] = mar[1] = mar[2] = mar[3] = 0 |
|
|
| const paddingBorderWidth = pad[0] + pad[2] + bor[0] + bor[2] |
| const paddingBorderHeight = pad[1] + pad[3] + bor[1] + bor[3] |
|
|
| |
| const styleWidth = forceWidth ? NaN : resolveValue(style.width, ownerWidth) |
| const styleHeight = forceHeight |
| ? NaN |
| : resolveValue(style.height, ownerHeight) |
|
|
| |
| let width = availableWidth |
| let height = availableHeight |
| let wMode = widthMode |
| let hMode = heightMode |
| if (isDefined(styleWidth)) { |
| width = styleWidth |
| wMode = MeasureMode.Exactly |
| } |
| if (isDefined(styleHeight)) { |
| height = styleHeight |
| hMode = MeasureMode.Exactly |
| } |
|
|
| |
| width = boundAxis(style, true, width, ownerWidth, ownerHeight) |
| height = boundAxis(style, false, height, ownerWidth, ownerHeight) |
|
|
| |
| if (node.measureFunc && node.children.length === 0) { |
| const innerW = |
| wMode === MeasureMode.Undefined |
| ? NaN |
| : Math.max(0, width - paddingBorderWidth) |
| const innerH = |
| hMode === MeasureMode.Undefined |
| ? NaN |
| : Math.max(0, height - paddingBorderHeight) |
| _yogaMeasureCalls++ |
| const measured = node.measureFunc(innerW, wMode, innerH, hMode) |
| node.layout.width = |
| wMode === MeasureMode.Exactly |
| ? width |
| : boundAxis( |
| style, |
| true, |
| (measured.width ?? 0) + paddingBorderWidth, |
| ownerWidth, |
| ownerHeight, |
| ) |
| node.layout.height = |
| hMode === MeasureMode.Exactly |
| ? height |
| : boundAxis( |
| style, |
| false, |
| (measured.height ?? 0) + paddingBorderHeight, |
| ownerWidth, |
| ownerHeight, |
| ) |
| commitCacheOutputs(node, performLayout) |
| |
| |
| |
| |
| |
| cacheWrite( |
| node, |
| availableWidth, |
| availableHeight, |
| widthMode, |
| heightMode, |
| ownerWidth, |
| ownerHeight, |
| forceWidth, |
| forceHeight, |
| wasDirty, |
| ) |
| return |
| } |
|
|
| |
| if (node.children.length === 0) { |
| node.layout.width = |
| wMode === MeasureMode.Exactly |
| ? width |
| : boundAxis(style, true, paddingBorderWidth, ownerWidth, ownerHeight) |
| node.layout.height = |
| hMode === MeasureMode.Exactly |
| ? height |
| : boundAxis(style, false, paddingBorderHeight, ownerWidth, ownerHeight) |
| commitCacheOutputs(node, performLayout) |
| |
| |
| |
| |
| |
| cacheWrite( |
| node, |
| availableWidth, |
| availableHeight, |
| widthMode, |
| heightMode, |
| ownerWidth, |
| ownerHeight, |
| forceWidth, |
| forceHeight, |
| wasDirty, |
| ) |
| return |
| } |
|
|
| |
| const mainAxis = style.flexDirection |
| const crossAx = crossAxis(mainAxis) |
| const isMainRow = isRow(mainAxis) |
|
|
| const mainSize = isMainRow ? width : height |
| const crossSize = isMainRow ? height : width |
| const mainMode = isMainRow ? wMode : hMode |
| const crossMode = isMainRow ? hMode : wMode |
| const mainPadBorder = isMainRow ? paddingBorderWidth : paddingBorderHeight |
| const crossPadBorder = isMainRow ? paddingBorderHeight : paddingBorderWidth |
|
|
| const innerMainSize = isDefined(mainSize) |
| ? Math.max(0, mainSize - mainPadBorder) |
| : NaN |
| const innerCrossSize = isDefined(crossSize) |
| ? Math.max(0, crossSize - crossPadBorder) |
| : NaN |
|
|
| |
| const gapMain = resolveGap( |
| style, |
| isMainRow ? Gutter.Column : Gutter.Row, |
| innerMainSize, |
| ) |
|
|
| |
| |
| |
| const flowChildren: Node[] = [] |
| const absChildren: Node[] = [] |
| collectLayoutChildren(node, flowChildren, absChildren) |
|
|
| |
| |
| |
| |
| const ownerW = isDefined(width) ? width : NaN |
| const ownerH = isDefined(height) ? height : NaN |
| const isWrap = style.flexWrap !== Wrap.NoWrap |
| const gapCross = resolveGap( |
| style, |
| isMainRow ? Gutter.Row : Gutter.Column, |
| innerCrossSize, |
| ) |
|
|
| |
| |
| |
| for (const c of flowChildren) { |
| c._flexBasis = computeFlexBasis( |
| c, |
| mainAxis, |
| innerMainSize, |
| innerCrossSize, |
| crossMode, |
| ownerW, |
| ownerH, |
| ) |
| } |
| const lines: Node[][] = [] |
| if (!isWrap || !isDefined(innerMainSize) || flowChildren.length === 0) { |
| for (const c of flowChildren) c._lineIndex = 0 |
| lines.push(flowChildren) |
| } else { |
| |
| |
| let lineStart = 0 |
| let lineLen = 0 |
| for (let i = 0; i < flowChildren.length; i++) { |
| const c = flowChildren[i]! |
| const hypo = boundAxis(c.style, isMainRow, c._flexBasis, ownerW, ownerH) |
| const outer = Math.max(0, hypo) + childMarginForAxis(c, mainAxis, ownerW) |
| const withGap = i > lineStart ? gapMain : 0 |
| if (i > lineStart && lineLen + withGap + outer > innerMainSize) { |
| lines.push(flowChildren.slice(lineStart, i)) |
| lineStart = i |
| lineLen = outer |
| } else { |
| lineLen += withGap + outer |
| } |
| c._lineIndex = lines.length |
| } |
| lines.push(flowChildren.slice(lineStart)) |
| } |
| const lineCount = lines.length |
| const isBaseline = isBaselineLayout(node, flowChildren) |
|
|
| |
| |
| const lineConsumedMain: number[] = new Array(lineCount) |
| const lineCrossSizes: number[] = new Array(lineCount) |
| |
| |
| const lineMaxAscent: number[] = isBaseline ? new Array(lineCount).fill(0) : [] |
| let maxLineMain = 0 |
| let totalLinesCross = 0 |
| for (let li = 0; li < lineCount; li++) { |
| const line = lines[li]! |
| const lineGap = line.length > 1 ? gapMain * (line.length - 1) : 0 |
| let lineBasis = lineGap |
| for (const c of line) { |
| lineBasis += c._flexBasis + childMarginForAxis(c, mainAxis, ownerW) |
| } |
| |
| |
| let availMain = innerMainSize |
| if (!isDefined(availMain)) { |
| const mainOwner = isMainRow ? ownerWidth : ownerHeight |
| const minM = resolveValue( |
| isMainRow ? style.minWidth : style.minHeight, |
| mainOwner, |
| ) |
| const maxM = resolveValue( |
| isMainRow ? style.maxWidth : style.maxHeight, |
| mainOwner, |
| ) |
| if (isDefined(maxM) && lineBasis > maxM - mainPadBorder) { |
| availMain = Math.max(0, maxM - mainPadBorder) |
| } else if (isDefined(minM) && lineBasis < minM - mainPadBorder) { |
| availMain = Math.max(0, minM - mainPadBorder) |
| } |
| } |
| resolveFlexibleLengths( |
| line, |
| availMain, |
| lineBasis, |
| isMainRow, |
| ownerW, |
| ownerH, |
| ) |
|
|
| |
| let lineCross = 0 |
| for (const c of line) { |
| const cStyle = c.style |
| const childAlign = |
| cStyle.alignSelf === Align.Auto ? style.alignItems : cStyle.alignSelf |
| const cMarginCross = childMarginForAxis(c, crossAx, ownerW) |
| let childCrossSize = NaN |
| let childCrossMode: MeasureMode = MeasureMode.Undefined |
| const resolvedCrossStyle = resolveValue( |
| isMainRow ? cStyle.height : cStyle.width, |
| isMainRow ? ownerH : ownerW, |
| ) |
| const crossLeadE = isMainRow ? EDGE_TOP : EDGE_LEFT |
| const crossTrailE = isMainRow ? EDGE_BOTTOM : EDGE_RIGHT |
| const hasCrossAutoMargin = |
| c._hasAutoMargin && |
| (isMarginAuto(cStyle.margin, crossLeadE) || |
| isMarginAuto(cStyle.margin, crossTrailE)) |
| |
| |
| |
| |
| if (isDefined(resolvedCrossStyle)) { |
| childCrossSize = resolvedCrossStyle |
| childCrossMode = MeasureMode.Exactly |
| } else if ( |
| childAlign === Align.Stretch && |
| !hasCrossAutoMargin && |
| !isWrap && |
| isDefined(innerCrossSize) && |
| crossMode === MeasureMode.Exactly |
| ) { |
| childCrossSize = Math.max(0, innerCrossSize - cMarginCross) |
| childCrossMode = MeasureMode.Exactly |
| } else if (!isWrap && isDefined(innerCrossSize)) { |
| childCrossSize = Math.max(0, innerCrossSize - cMarginCross) |
| childCrossMode = MeasureMode.AtMost |
| } |
| const cw = isMainRow ? c._mainSize : childCrossSize |
| const ch = isMainRow ? childCrossSize : c._mainSize |
| layoutNode( |
| c, |
| cw, |
| ch, |
| isMainRow ? MeasureMode.Exactly : childCrossMode, |
| isMainRow ? childCrossMode : MeasureMode.Exactly, |
| ownerW, |
| ownerH, |
| performLayout, |
| isMainRow, |
| !isMainRow, |
| ) |
| c._crossSize = isMainRow ? c.layout.height : c.layout.width |
| lineCross = Math.max(lineCross, c._crossSize + cMarginCross) |
| } |
| |
| |
| if (isBaseline) { |
| let maxAscent = 0 |
| let maxDescent = 0 |
| for (const c of line) { |
| if (resolveChildAlign(node, c) !== Align.Baseline) continue |
| const mTop = resolveEdge(c.style.margin, EDGE_TOP, ownerW) |
| const mBot = resolveEdge(c.style.margin, EDGE_BOTTOM, ownerW) |
| const ascent = calculateBaseline(c) + mTop |
| const descent = c.layout.height + mTop + mBot - ascent |
| if (ascent > maxAscent) maxAscent = ascent |
| if (descent > maxDescent) maxDescent = descent |
| } |
| lineMaxAscent[li] = maxAscent |
| if (maxAscent + maxDescent > lineCross) { |
| lineCross = maxAscent + maxDescent |
| } |
| } |
| |
| |
| |
| const mainLead = leadingEdge(mainAxis) |
| const mainTrail = trailingEdge(mainAxis) |
| let consumed = lineGap |
| for (const c of line) { |
| const cm = c.layout.margin |
| consumed += c._mainSize + cm[mainLead]! + cm[mainTrail]! |
| } |
| lineConsumedMain[li] = consumed |
| lineCrossSizes[li] = lineCross |
| maxLineMain = Math.max(maxLineMain, consumed) |
| totalLinesCross += lineCross |
| } |
| const totalCrossGap = lineCount > 1 ? gapCross * (lineCount - 1) : 0 |
| totalLinesCross += totalCrossGap |
|
|
| |
| |
| |
| |
| |
| |
| const isScroll = style.overflow === Overflow.Scroll |
| const contentMain = maxLineMain + mainPadBorder |
| const finalMainSize = |
| mainMode === MeasureMode.Exactly |
| ? mainSize |
| : mainMode === MeasureMode.AtMost && isScroll |
| ? Math.max(Math.min(mainSize, contentMain), mainPadBorder) |
| : isWrap && lineCount > 1 && mainMode === MeasureMode.AtMost |
| ? mainSize |
| : contentMain |
| const contentCross = totalLinesCross + crossPadBorder |
| const finalCrossSize = |
| crossMode === MeasureMode.Exactly |
| ? crossSize |
| : crossMode === MeasureMode.AtMost && isScroll |
| ? Math.max(Math.min(crossSize, contentCross), crossPadBorder) |
| : contentCross |
| node.layout.width = boundAxis( |
| style, |
| true, |
| isMainRow ? finalMainSize : finalCrossSize, |
| ownerWidth, |
| ownerHeight, |
| ) |
| node.layout.height = boundAxis( |
| style, |
| false, |
| isMainRow ? finalCrossSize : finalMainSize, |
| ownerWidth, |
| ownerHeight, |
| ) |
| commitCacheOutputs(node, performLayout) |
| |
| cacheWrite( |
| node, |
| availableWidth, |
| availableHeight, |
| widthMode, |
| heightMode, |
| ownerWidth, |
| ownerHeight, |
| forceWidth, |
| forceHeight, |
| wasDirty, |
| ) |
|
|
| if (!performLayout) return |
|
|
| |
| |
| const actualInnerMain = |
| (isMainRow ? node.layout.width : node.layout.height) - mainPadBorder |
| const actualInnerCross = |
| (isMainRow ? node.layout.height : node.layout.width) - crossPadBorder |
| const mainLeadEdgePhys = leadingEdge(mainAxis) |
| const mainTrailEdgePhys = trailingEdge(mainAxis) |
| const crossLeadEdgePhys = isMainRow ? EDGE_TOP : EDGE_LEFT |
| const crossTrailEdgePhys = isMainRow ? EDGE_BOTTOM : EDGE_RIGHT |
| const reversed = isReverse(mainAxis) |
| const mainContainerSize = isMainRow ? node.layout.width : node.layout.height |
| const crossLead = pad[crossLeadEdgePhys]! + bor[crossLeadEdgePhys]! |
|
|
| |
| |
| |
| let lineCrossOffset = crossLead |
| let betweenLines = gapCross |
| const freeCross = actualInnerCross - totalLinesCross |
| if (lineCount === 1 && !isWrap && !isBaseline) { |
| lineCrossSizes[0] = actualInnerCross |
| } else { |
| const remCross = Math.max(0, freeCross) |
| switch (style.alignContent) { |
| case Align.FlexStart: |
| break |
| case Align.Center: |
| lineCrossOffset += freeCross / 2 |
| break |
| case Align.FlexEnd: |
| lineCrossOffset += freeCross |
| break |
| case Align.Stretch: |
| if (lineCount > 0 && remCross > 0) { |
| const add = remCross / lineCount |
| for (let i = 0; i < lineCount; i++) lineCrossSizes[i]! += add |
| } |
| break |
| case Align.SpaceBetween: |
| if (lineCount > 1) betweenLines += remCross / (lineCount - 1) |
| break |
| case Align.SpaceAround: |
| if (lineCount > 0) { |
| betweenLines += remCross / lineCount |
| lineCrossOffset += remCross / lineCount / 2 |
| } |
| break |
| case Align.SpaceEvenly: |
| if (lineCount > 0) { |
| betweenLines += remCross / (lineCount + 1) |
| lineCrossOffset += remCross / (lineCount + 1) |
| } |
| break |
| default: |
| break |
| } |
| } |
|
|
| |
| |
| const wrapReverse = style.flexWrap === Wrap.WrapReverse |
| const crossContainerSize = isMainRow ? node.layout.height : node.layout.width |
| let lineCrossPos = lineCrossOffset |
| for (let li = 0; li < lineCount; li++) { |
| const line = lines[li]! |
| const lineCross = lineCrossSizes[li]! |
| const consumedMain = lineConsumedMain[li]! |
| const n = line.length |
|
|
| |
| |
| |
| |
| |
| if (isWrap || crossMode !== MeasureMode.Exactly) { |
| for (const c of line) { |
| const cStyle = c.style |
| const childAlign = |
| cStyle.alignSelf === Align.Auto ? style.alignItems : cStyle.alignSelf |
| const crossStyleDef = isDefined( |
| resolveValue( |
| isMainRow ? cStyle.height : cStyle.width, |
| isMainRow ? ownerH : ownerW, |
| ), |
| ) |
| const hasCrossAutoMargin = |
| c._hasAutoMargin && |
| (isMarginAuto(cStyle.margin, crossLeadEdgePhys) || |
| isMarginAuto(cStyle.margin, crossTrailEdgePhys)) |
| if ( |
| childAlign === Align.Stretch && |
| !crossStyleDef && |
| !hasCrossAutoMargin |
| ) { |
| const cMarginCross = childMarginForAxis(c, crossAx, ownerW) |
| const target = Math.max(0, lineCross - cMarginCross) |
| if (c._crossSize !== target) { |
| const cw = isMainRow ? c._mainSize : target |
| const ch = isMainRow ? target : c._mainSize |
| layoutNode( |
| c, |
| cw, |
| ch, |
| MeasureMode.Exactly, |
| MeasureMode.Exactly, |
| ownerW, |
| ownerH, |
| performLayout, |
| isMainRow, |
| !isMainRow, |
| ) |
| c._crossSize = target |
| } |
| } |
| } |
| } |
|
|
| |
| let mainOffset = pad[mainLeadEdgePhys]! + bor[mainLeadEdgePhys]! |
| let betweenMain = gapMain |
| let numAutoMarginsMain = 0 |
| for (const c of line) { |
| if (!c._hasAutoMargin) continue |
| if (isMarginAuto(c.style.margin, mainLeadEdgePhys)) numAutoMarginsMain++ |
| if (isMarginAuto(c.style.margin, mainTrailEdgePhys)) numAutoMarginsMain++ |
| } |
| const freeMain = actualInnerMain - consumedMain |
| const remainingMain = Math.max(0, freeMain) |
| const autoMarginMainSize = |
| numAutoMarginsMain > 0 && remainingMain > 0 |
| ? remainingMain / numAutoMarginsMain |
| : 0 |
| if (numAutoMarginsMain === 0) { |
| switch (style.justifyContent) { |
| case Justify.FlexStart: |
| break |
| case Justify.Center: |
| mainOffset += freeMain / 2 |
| break |
| case Justify.FlexEnd: |
| mainOffset += freeMain |
| break |
| case Justify.SpaceBetween: |
| if (n > 1) betweenMain += remainingMain / (n - 1) |
| break |
| case Justify.SpaceAround: |
| if (n > 0) { |
| betweenMain += remainingMain / n |
| mainOffset += remainingMain / n / 2 |
| } |
| break |
| case Justify.SpaceEvenly: |
| if (n > 0) { |
| betweenMain += remainingMain / (n + 1) |
| mainOffset += remainingMain / (n + 1) |
| } |
| break |
| } |
| } |
|
|
| const effectiveLineCrossPos = wrapReverse |
| ? crossContainerSize - lineCrossPos - lineCross |
| : lineCrossPos |
|
|
| let pos = mainOffset |
| for (const c of line) { |
| const cMargin = c.style.margin |
| |
| |
| |
| |
| |
| const cLayoutMargin = c.layout.margin |
| let autoMainLead = false |
| let autoMainTrail = false |
| let autoCrossLead = false |
| let autoCrossTrail = false |
| let mMainLead: number |
| let mMainTrail: number |
| let mCrossLead: number |
| let mCrossTrail: number |
| if (c._hasAutoMargin) { |
| autoMainLead = isMarginAuto(cMargin, mainLeadEdgePhys) |
| autoMainTrail = isMarginAuto(cMargin, mainTrailEdgePhys) |
| autoCrossLead = isMarginAuto(cMargin, crossLeadEdgePhys) |
| autoCrossTrail = isMarginAuto(cMargin, crossTrailEdgePhys) |
| mMainLead = autoMainLead |
| ? autoMarginMainSize |
| : cLayoutMargin[mainLeadEdgePhys]! |
| mMainTrail = autoMainTrail |
| ? autoMarginMainSize |
| : cLayoutMargin[mainTrailEdgePhys]! |
| mCrossLead = autoCrossLead ? 0 : cLayoutMargin[crossLeadEdgePhys]! |
| mCrossTrail = autoCrossTrail ? 0 : cLayoutMargin[crossTrailEdgePhys]! |
| } else { |
| |
| mMainLead = cLayoutMargin[mainLeadEdgePhys]! |
| mMainTrail = cLayoutMargin[mainTrailEdgePhys]! |
| mCrossLead = cLayoutMargin[crossLeadEdgePhys]! |
| mCrossTrail = cLayoutMargin[crossTrailEdgePhys]! |
| } |
|
|
| const mainPos = reversed |
| ? mainContainerSize - (pos + mMainLead) - c._mainSize |
| : pos + mMainLead |
|
|
| const childAlign = |
| c.style.alignSelf === Align.Auto ? style.alignItems : c.style.alignSelf |
| let crossPos = effectiveLineCrossPos + mCrossLead |
| const crossFree = lineCross - c._crossSize - mCrossLead - mCrossTrail |
| if (autoCrossLead && autoCrossTrail) { |
| crossPos += Math.max(0, crossFree) / 2 |
| } else if (autoCrossLead) { |
| crossPos += Math.max(0, crossFree) |
| } else if (autoCrossTrail) { |
| |
| } else { |
| switch (childAlign) { |
| case Align.FlexStart: |
| case Align.Stretch: |
| if (wrapReverse) crossPos += crossFree |
| break |
| case Align.Center: |
| crossPos += crossFree / 2 |
| break |
| case Align.FlexEnd: |
| if (!wrapReverse) crossPos += crossFree |
| break |
| case Align.Baseline: |
| |
| |
| |
| if (isBaseline) { |
| crossPos = |
| effectiveLineCrossPos + |
| lineMaxAscent[li]! - |
| calculateBaseline(c) |
| } |
| break |
| default: |
| break |
| } |
| } |
|
|
| |
| |
| let relX = 0 |
| let relY = 0 |
| if (c._hasPosition) { |
| const relLeft = resolveValue( |
| resolveEdgeRaw(c.style.position, EDGE_LEFT), |
| ownerW, |
| ) |
| const relRight = resolveValue( |
| resolveEdgeRaw(c.style.position, EDGE_RIGHT), |
| ownerW, |
| ) |
| const relTop = resolveValue( |
| resolveEdgeRaw(c.style.position, EDGE_TOP), |
| ownerW, |
| ) |
| const relBottom = resolveValue( |
| resolveEdgeRaw(c.style.position, EDGE_BOTTOM), |
| ownerW, |
| ) |
| relX = isDefined(relLeft) |
| ? relLeft |
| : isDefined(relRight) |
| ? -relRight |
| : 0 |
| relY = isDefined(relTop) |
| ? relTop |
| : isDefined(relBottom) |
| ? -relBottom |
| : 0 |
| } |
|
|
| if (isMainRow) { |
| c.layout.left = mainPos + relX |
| c.layout.top = crossPos + relY |
| } else { |
| c.layout.left = crossPos + relX |
| c.layout.top = mainPos + relY |
| } |
| pos += c._mainSize + mMainLead + mMainTrail + betweenMain |
| } |
| lineCrossPos += lineCross + betweenLines |
| } |
|
|
| |
| for (const c of absChildren) { |
| layoutAbsoluteChild( |
| node, |
| c, |
| node.layout.width, |
| node.layout.height, |
| pad, |
| bor, |
| ) |
| } |
| } |
|
|
| function layoutAbsoluteChild( |
| parent: Node, |
| child: Node, |
| parentWidth: number, |
| parentHeight: number, |
| pad: [number, number, number, number], |
| bor: [number, number, number, number], |
| ): void { |
| const cs = child.style |
| const posLeft = resolveEdgeRaw(cs.position, EDGE_LEFT) |
| const posRight = resolveEdgeRaw(cs.position, EDGE_RIGHT) |
| const posTop = resolveEdgeRaw(cs.position, EDGE_TOP) |
| const posBottom = resolveEdgeRaw(cs.position, EDGE_BOTTOM) |
|
|
| const rLeft = resolveValue(posLeft, parentWidth) |
| const rRight = resolveValue(posRight, parentWidth) |
| const rTop = resolveValue(posTop, parentHeight) |
| const rBottom = resolveValue(posBottom, parentHeight) |
|
|
| |
| |
| const paddingBoxW = parentWidth - bor[0] - bor[2] |
| const paddingBoxH = parentHeight - bor[1] - bor[3] |
| let cw = resolveValue(cs.width, paddingBoxW) |
| let ch = resolveValue(cs.height, paddingBoxH) |
|
|
| |
| if (!isDefined(cw) && isDefined(rLeft) && isDefined(rRight)) { |
| cw = paddingBoxW - rLeft - rRight |
| } |
| if (!isDefined(ch) && isDefined(rTop) && isDefined(rBottom)) { |
| ch = paddingBoxH - rTop - rBottom |
| } |
|
|
| layoutNode( |
| child, |
| cw, |
| ch, |
| isDefined(cw) ? MeasureMode.Exactly : MeasureMode.Undefined, |
| isDefined(ch) ? MeasureMode.Exactly : MeasureMode.Undefined, |
| paddingBoxW, |
| paddingBoxH, |
| true, |
| ) |
|
|
| |
| const mL = resolveEdge(cs.margin, EDGE_LEFT, parentWidth) |
| const mT = resolveEdge(cs.margin, EDGE_TOP, parentWidth) |
| const mR = resolveEdge(cs.margin, EDGE_RIGHT, parentWidth) |
| const mB = resolveEdge(cs.margin, EDGE_BOTTOM, parentWidth) |
|
|
| const mainAxis = parent.style.flexDirection |
| const reversed = isReverse(mainAxis) |
| const mainRow = isRow(mainAxis) |
| const wrapReverse = parent.style.flexWrap === Wrap.WrapReverse |
| |
| const alignment = |
| cs.alignSelf === Align.Auto ? parent.style.alignItems : cs.alignSelf |
|
|
| |
| let left: number |
| if (isDefined(rLeft)) { |
| left = bor[0] + rLeft + mL |
| } else if (isDefined(rRight)) { |
| left = parentWidth - bor[2] - rRight - child.layout.width - mR |
| } else if (mainRow) { |
| |
| const lead = pad[0] + bor[0] |
| const trail = parentWidth - pad[2] - bor[2] |
| left = reversed |
| ? trail - child.layout.width - mR |
| : justifyAbsolute( |
| parent.style.justifyContent, |
| lead, |
| trail, |
| child.layout.width, |
| ) + mL |
| } else { |
| left = |
| alignAbsolute( |
| alignment, |
| pad[0] + bor[0], |
| parentWidth - pad[2] - bor[2], |
| child.layout.width, |
| wrapReverse, |
| ) + mL |
| } |
|
|
| let top: number |
| if (isDefined(rTop)) { |
| top = bor[1] + rTop + mT |
| } else if (isDefined(rBottom)) { |
| top = parentHeight - bor[3] - rBottom - child.layout.height - mB |
| } else if (mainRow) { |
| top = |
| alignAbsolute( |
| alignment, |
| pad[1] + bor[1], |
| parentHeight - pad[3] - bor[3], |
| child.layout.height, |
| wrapReverse, |
| ) + mT |
| } else { |
| const lead = pad[1] + bor[1] |
| const trail = parentHeight - pad[3] - bor[3] |
| top = reversed |
| ? trail - child.layout.height - mB |
| : justifyAbsolute( |
| parent.style.justifyContent, |
| lead, |
| trail, |
| child.layout.height, |
| ) + mT |
| } |
|
|
| child.layout.left = left |
| child.layout.top = top |
| } |
|
|
| function justifyAbsolute( |
| justify: Justify, |
| leadEdge: number, |
| trailEdge: number, |
| childSize: number, |
| ): number { |
| switch (justify) { |
| case Justify.Center: |
| return leadEdge + (trailEdge - leadEdge - childSize) / 2 |
| case Justify.FlexEnd: |
| return trailEdge - childSize |
| default: |
| return leadEdge |
| } |
| } |
|
|
| function alignAbsolute( |
| align: Align, |
| leadEdge: number, |
| trailEdge: number, |
| childSize: number, |
| wrapReverse: boolean, |
| ): number { |
| |
| |
| |
| switch (align) { |
| case Align.Center: |
| return leadEdge + (trailEdge - leadEdge - childSize) / 2 |
| case Align.FlexEnd: |
| return wrapReverse ? leadEdge : trailEdge - childSize |
| default: |
| return wrapReverse ? trailEdge - childSize : leadEdge |
| } |
| } |
|
|
| function computeFlexBasis( |
| child: Node, |
| mainAxis: FlexDirection, |
| availableMain: number, |
| availableCross: number, |
| crossMode: MeasureMode, |
| ownerWidth: number, |
| ownerHeight: number, |
| ): number { |
| |
| |
| |
| |
| |
| |
| |
| |
| const sameGen = child._fbGen === _generation |
| if ( |
| (sameGen || !child.isDirty_) && |
| child._fbCrossMode === crossMode && |
| sameFloat(child._fbOwnerW, ownerWidth) && |
| sameFloat(child._fbOwnerH, ownerHeight) && |
| sameFloat(child._fbAvailMain, availableMain) && |
| sameFloat(child._fbAvailCross, availableCross) |
| ) { |
| return child._fbBasis |
| } |
| const cs = child.style |
| const isMainRow = isRow(mainAxis) |
|
|
| |
| const basis = resolveValue(cs.flexBasis, availableMain) |
| if (isDefined(basis)) { |
| const b = Math.max(0, basis) |
| child._fbBasis = b |
| child._fbOwnerW = ownerWidth |
| child._fbOwnerH = ownerHeight |
| child._fbAvailMain = availableMain |
| child._fbAvailCross = availableCross |
| child._fbCrossMode = crossMode |
| child._fbGen = _generation |
| return b |
| } |
|
|
| |
| const mainStyleDim = isMainRow ? cs.width : cs.height |
| const mainOwner = isMainRow ? ownerWidth : ownerHeight |
| const resolved = resolveValue(mainStyleDim, mainOwner) |
| if (isDefined(resolved)) { |
| const b = Math.max(0, resolved) |
| child._fbBasis = b |
| child._fbOwnerW = ownerWidth |
| child._fbOwnerH = ownerHeight |
| child._fbAvailMain = availableMain |
| child._fbAvailCross = availableCross |
| child._fbCrossMode = crossMode |
| child._fbGen = _generation |
| return b |
| } |
|
|
| |
| const crossStyleDim = isMainRow ? cs.height : cs.width |
| const crossOwner = isMainRow ? ownerHeight : ownerWidth |
| let crossConstraint = resolveValue(crossStyleDim, crossOwner) |
| let crossConstraintMode: MeasureMode = isDefined(crossConstraint) |
| ? MeasureMode.Exactly |
| : MeasureMode.Undefined |
| if (!isDefined(crossConstraint) && isDefined(availableCross)) { |
| crossConstraint = availableCross |
| crossConstraintMode = |
| crossMode === MeasureMode.Exactly && isStretchAlign(child) |
| ? MeasureMode.Exactly |
| : MeasureMode.AtMost |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| let mainConstraint = NaN |
| let mainConstraintMode: MeasureMode = MeasureMode.Undefined |
| if (isMainRow && isDefined(availableMain) && hasMeasureFuncInSubtree(child)) { |
| mainConstraint = availableMain |
| mainConstraintMode = MeasureMode.AtMost |
| } |
|
|
| const mw = isMainRow ? mainConstraint : crossConstraint |
| const mh = isMainRow ? crossConstraint : mainConstraint |
| const mwMode = isMainRow ? mainConstraintMode : crossConstraintMode |
| const mhMode = isMainRow ? crossConstraintMode : mainConstraintMode |
|
|
| layoutNode(child, mw, mh, mwMode, mhMode, ownerWidth, ownerHeight, false) |
| const b = isMainRow ? child.layout.width : child.layout.height |
| child._fbBasis = b |
| child._fbOwnerW = ownerWidth |
| child._fbOwnerH = ownerHeight |
| child._fbAvailMain = availableMain |
| child._fbAvailCross = availableCross |
| child._fbCrossMode = crossMode |
| child._fbGen = _generation |
| return b |
| } |
|
|
| function hasMeasureFuncInSubtree(node: Node): boolean { |
| if (node.measureFunc) return true |
| for (const c of node.children) { |
| if (hasMeasureFuncInSubtree(c)) return true |
| } |
| return false |
| } |
|
|
| function resolveFlexibleLengths( |
| children: Node[], |
| availableInnerMain: number, |
| totalFlexBasis: number, |
| isMainRow: boolean, |
| ownerW: number, |
| ownerH: number, |
| ): void { |
| |
| |
| |
| const n = children.length |
| const frozen: boolean[] = new Array(n).fill(false) |
| const initialFree = isDefined(availableInnerMain) |
| ? availableInnerMain - totalFlexBasis |
| : 0 |
| |
| for (let i = 0; i < n; i++) { |
| const c = children[i]! |
| const clamped = boundAxis(c.style, isMainRow, c._flexBasis, ownerW, ownerH) |
| const inflexible = |
| !isDefined(availableInnerMain) || |
| (initialFree >= 0 ? c.style.flexGrow === 0 : c.style.flexShrink === 0) |
| if (inflexible) { |
| c._mainSize = Math.max(0, clamped) |
| frozen[i] = true |
| } else { |
| c._mainSize = c._flexBasis |
| } |
| } |
| |
| |
| |
| const unclamped: number[] = new Array(n) |
| for (let iter = 0; iter <= n; iter++) { |
| let frozenDelta = 0 |
| let totalGrow = 0 |
| let totalShrinkScaled = 0 |
| let unfrozenCount = 0 |
| for (let i = 0; i < n; i++) { |
| const c = children[i]! |
| if (frozen[i]) { |
| frozenDelta += c._mainSize - c._flexBasis |
| } else { |
| totalGrow += c.style.flexGrow |
| totalShrinkScaled += c.style.flexShrink * c._flexBasis |
| unfrozenCount++ |
| } |
| } |
| if (unfrozenCount === 0) break |
| let remaining = initialFree - frozenDelta |
| |
| |
| if (remaining > 0 && totalGrow > 0 && totalGrow < 1) { |
| const scaled = initialFree * totalGrow |
| if (scaled < remaining) remaining = scaled |
| } else if (remaining < 0 && totalShrinkScaled > 0) { |
| let totalShrink = 0 |
| for (let i = 0; i < n; i++) { |
| if (!frozen[i]) totalShrink += children[i]!.style.flexShrink |
| } |
| if (totalShrink < 1) { |
| const scaled = initialFree * totalShrink |
| if (scaled > remaining) remaining = scaled |
| } |
| } |
| |
| let totalViolation = 0 |
| for (let i = 0; i < n; i++) { |
| if (frozen[i]) continue |
| const c = children[i]! |
| let t = c._flexBasis |
| if (remaining > 0 && totalGrow > 0) { |
| t += (remaining * c.style.flexGrow) / totalGrow |
| } else if (remaining < 0 && totalShrinkScaled > 0) { |
| t += |
| (remaining * (c.style.flexShrink * c._flexBasis)) / totalShrinkScaled |
| } |
| unclamped[i] = t |
| const clamped = Math.max( |
| 0, |
| boundAxis(c.style, isMainRow, t, ownerW, ownerH), |
| ) |
| c._mainSize = clamped |
| totalViolation += clamped - t |
| } |
| |
| |
| if (totalViolation === 0) break |
| let anyFrozen = false |
| for (let i = 0; i < n; i++) { |
| if (frozen[i]) continue |
| const v = children[i]!._mainSize - unclamped[i]! |
| if ((totalViolation > 0 && v > 0) || (totalViolation < 0 && v < 0)) { |
| frozen[i] = true |
| anyFrozen = true |
| } |
| } |
| if (!anyFrozen) break |
| } |
| } |
|
|
| function isStretchAlign(child: Node): boolean { |
| const p = child.parent |
| if (!p) return false |
| const align = |
| child.style.alignSelf === Align.Auto |
| ? p.style.alignItems |
| : child.style.alignSelf |
| return align === Align.Stretch |
| } |
|
|
| function resolveChildAlign(parent: Node, child: Node): Align { |
| return child.style.alignSelf === Align.Auto |
| ? parent.style.alignItems |
| : child.style.alignSelf |
| } |
|
|
| |
| |
| |
| |
| function calculateBaseline(node: Node): number { |
| let baselineChild: Node | null = null |
| for (const c of node.children) { |
| if (c._lineIndex > 0) break |
| if (c.style.positionType === PositionType.Absolute) continue |
| if (c.style.display === Display.None) continue |
| if ( |
| resolveChildAlign(node, c) === Align.Baseline || |
| c.isReferenceBaseline_ |
| ) { |
| baselineChild = c |
| break |
| } |
| if (baselineChild === null) baselineChild = c |
| } |
| if (baselineChild === null) return node.layout.height |
| return calculateBaseline(baselineChild) + baselineChild.layout.top |
| } |
|
|
| |
| |
| function isBaselineLayout(node: Node, flowChildren: Node[]): boolean { |
| if (!isRow(node.style.flexDirection)) return false |
| if (node.style.alignItems === Align.Baseline) return true |
| for (const c of flowChildren) { |
| if (c.style.alignSelf === Align.Baseline) return true |
| } |
| return false |
| } |
|
|
| function childMarginForAxis( |
| child: Node, |
| axis: FlexDirection, |
| ownerWidth: number, |
| ): number { |
| if (!child._hasMargin) return 0 |
| const lead = resolveEdge(child.style.margin, leadingEdge(axis), ownerWidth) |
| const trail = resolveEdge(child.style.margin, trailingEdge(axis), ownerWidth) |
| return lead + trail |
| } |
|
|
| function resolveGap(style: Style, gutter: Gutter, ownerSize: number): number { |
| let v = style.gap[gutter]! |
| if (v.unit === Unit.Undefined) v = style.gap[Gutter.All]! |
| const r = resolveValue(v, ownerSize) |
| return isDefined(r) ? Math.max(0, r) : 0 |
| } |
|
|
| function boundAxis( |
| style: Style, |
| isWidth: boolean, |
| value: number, |
| ownerWidth: number, |
| ownerHeight: number, |
| ): number { |
| const minV = isWidth ? style.minWidth : style.minHeight |
| const maxV = isWidth ? style.maxWidth : style.maxHeight |
| const minU = minV.unit |
| const maxU = maxV.unit |
| |
| |
| |
| |
| if (minU === 0 && maxU === 0) return value |
| const owner = isWidth ? ownerWidth : ownerHeight |
| let v = value |
| |
| if (maxU === 1) { |
| if (v > maxV.value) v = maxV.value |
| } else if (maxU === 2) { |
| const m = (maxV.value * owner) / 100 |
| if (m === m && v > m) v = m |
| } |
| if (minU === 1) { |
| if (v < minV.value) v = minV.value |
| } else if (minU === 2) { |
| const m = (minV.value * owner) / 100 |
| if (m === m && v < m) v = m |
| } |
| return v |
| } |
|
|
| function zeroLayoutRecursive(node: Node): void { |
| for (const c of node.children) { |
| c.layout.left = 0 |
| c.layout.top = 0 |
| c.layout.width = 0 |
| c.layout.height = 0 |
| |
| |
| |
| |
| |
| |
| |
| c.isDirty_ = true |
| c._hasL = false |
| c._hasM = false |
| zeroLayoutRecursive(c) |
| } |
| } |
|
|
| function collectLayoutChildren(node: Node, flow: Node[], abs: Node[]): void { |
| |
| |
| |
| |
| for (const c of node.children) { |
| const disp = c.style.display |
| if (disp === Display.None) { |
| c.layout.left = 0 |
| c.layout.top = 0 |
| c.layout.width = 0 |
| c.layout.height = 0 |
| zeroLayoutRecursive(c) |
| } else if (disp === Display.Contents) { |
| c.layout.left = 0 |
| c.layout.top = 0 |
| c.layout.width = 0 |
| c.layout.height = 0 |
| |
| |
| collectLayoutChildren(c, flow, abs) |
| } else if (c.style.positionType === PositionType.Absolute) { |
| abs.push(c) |
| } else { |
| flow.push(c) |
| } |
| } |
| } |
|
|
| function roundLayout( |
| node: Node, |
| scale: number, |
| absLeft: number, |
| absTop: number, |
| ): void { |
| if (scale === 0) return |
| const l = node.layout |
| const nodeLeft = l.left |
| const nodeTop = l.top |
| const nodeWidth = l.width |
| const nodeHeight = l.height |
|
|
| const absNodeLeft = absLeft + nodeLeft |
| const absNodeTop = absTop + nodeTop |
|
|
| |
| |
| |
| |
| |
| |
| const isText = node.measureFunc !== null |
| l.left = roundValue(nodeLeft, scale, false, isText) |
| l.top = roundValue(nodeTop, scale, false, isText) |
|
|
| |
| const absRight = absNodeLeft + nodeWidth |
| const absBottom = absNodeTop + nodeHeight |
| const hasFracW = !isWholeNumber(nodeWidth * scale) |
| const hasFracH = !isWholeNumber(nodeHeight * scale) |
| l.width = |
| roundValue(absRight, scale, isText && hasFracW, isText && !hasFracW) - |
| roundValue(absNodeLeft, scale, false, isText) |
| l.height = |
| roundValue(absBottom, scale, isText && hasFracH, isText && !hasFracH) - |
| roundValue(absNodeTop, scale, false, isText) |
|
|
| for (const c of node.children) { |
| roundLayout(c, scale, absNodeLeft, absNodeTop) |
| } |
| } |
|
|
| function isWholeNumber(v: number): boolean { |
| const frac = v - Math.floor(v) |
| return frac < 0.0001 || frac > 0.9999 |
| } |
|
|
| function roundValue( |
| v: number, |
| scale: number, |
| forceCeil: boolean, |
| forceFloor: boolean, |
| ): number { |
| let scaled = v * scale |
| let frac = scaled - Math.floor(scaled) |
| if (frac < 0) frac += 1 |
| |
| if (frac < 0.0001) { |
| scaled = Math.floor(scaled) |
| } else if (frac > 0.9999) { |
| scaled = Math.ceil(scaled) |
| } else if (forceCeil) { |
| scaled = Math.ceil(scaled) |
| } else if (forceFloor) { |
| scaled = Math.floor(scaled) |
| } else { |
| |
| scaled = Math.floor(scaled) + (frac >= 0.4999 ? 1 : 0) |
| } |
| return scaled / scale |
| } |
|
|
| |
| |
|
|
| function parseDimension(v: number | string | undefined): Value { |
| if (v === undefined) return UNDEFINED_VALUE |
| if (v === 'auto') return AUTO_VALUE |
| if (typeof v === 'number') { |
| |
| |
| |
| |
| return Number.isFinite(v) ? pointValue(v) : UNDEFINED_VALUE |
| } |
| if (typeof v === 'string' && v.endsWith('%')) { |
| return percentValue(parseFloat(v)) |
| } |
| const n = parseFloat(v) |
| return isNaN(n) ? UNDEFINED_VALUE : pointValue(n) |
| } |
|
|
| function physicalEdge(edge: Edge): number { |
| switch (edge) { |
| case Edge.Left: |
| case Edge.Start: |
| return EDGE_LEFT |
| case Edge.Top: |
| return EDGE_TOP |
| case Edge.Right: |
| case Edge.End: |
| return EDGE_RIGHT |
| case Edge.Bottom: |
| return EDGE_BOTTOM |
| default: |
| return EDGE_LEFT |
| } |
| } |
|
|
| |
| |
|
|
| export type Yoga = { |
| Config: { |
| create(): Config |
| destroy(config: Config): void |
| } |
| Node: { |
| create(config?: Config): Node |
| createDefault(): Node |
| createWithConfig(config: Config): Node |
| destroy(node: Node): void |
| } |
| } |
|
|
| const YOGA_INSTANCE: Yoga = { |
| Config: { |
| create: createConfig, |
| destroy() {}, |
| }, |
| Node: { |
| create: (config?: Config) => new Node(config), |
| createDefault: () => new Node(), |
| createWithConfig: (config: Config) => new Node(config), |
| destroy() {}, |
| }, |
| } |
|
|
| export function loadYoga(): Promise<Yoga> { |
| return Promise.resolve(YOGA_INSTANCE) |
| } |
|
|
| export default YOGA_INSTANCE |
|
|