Compare commits

..

14 Commits

Author SHA1 Message Date
Nick Gerleman
3a96c7717d Bump to 2.0.1 2023-10-31 21:49:09 -07:00
Nick Gerleman
c01dcd2338 Fix yoga-layout/wasm-sync exporting async binary if browser/node export conditions not met
When node or browser export conditions are not met, we resolve `yoga-layout/wasm-sync` to the async binary because of a typo.
2023-10-31 21:48:56 -07:00
Nick Gerleman
83a58bb9d6 Fixup packages 2023-07-26 22:28:21 -07:00
Nick Gerleman
30b697d3fe ./set-version.py 2.0.0 2023-07-25 09:47:27 -07:00
Nick Gerleman
11721f487d ./set-version.py 2.0.0-beta.2 2023-07-12 18:07:57 -07:00
Nick Gerleman
6570e43a83 Add blurb about configuring TypeScript for ESM resolution (#1330)
Summary:
Pull Request resolved: https://github.com/facebook/yoga/pull/1330

This has been tripping folks up a bit. Add a blurb to the package README to call it out.

Reviewed By: cortinico

Differential Revision: D47370333

fbshipit-source-id: f07b2e4d0e23865e3554c7aaf6ec77dff71a7423
2023-07-12 18:04:47 -07:00
Nick Gerleman
6628873c2a Separate Node and Web Binaries (#1325)
Summary:
Pull Request resolved: https://github.com/facebook/yoga/pull/1325

Fixes https://github.com/facebook/yoga/issues/1321

Reviewed By: cortinico

Differential Revision: D47368830

fbshipit-source-id: 570a45ad7fd182ef82e6edda4037ae2f6faa0c75
2023-07-12 18:04:17 -07:00
Nicola Corti
edf91b245d Actually hit Maven Central when publishing tags
Summary:
Due to a copypasta error, the tags will keep on publishing to the snapshot repo.
I've updated the config to actually hit Maven Central.

Reviewed By: NickGerleman

Differential Revision: D47150549

fbshipit-source-id: 314b931c1c9203ef53e1433a88bcc00609abd3d0
2023-06-30 13:37:51 -07:00
Nick Gerleman
a4c339264a [local] Workflow syntax fix 2023-06-29 13:18:49 -07:00
Nick Gerleman
9a50bd7d61 [local] Change how we pass npm auth token 2023-06-29 13:04:08 -07:00
Nick Gerleman
1b3ddbbf4a [local] publish YogaKit with --synchronous 2023-06-29 11:54:59 -07:00
Nick Gerleman
24d2782ad3 [local] Fix workflow syntax for npm 2023-06-29 11:38:07 -07:00
Nick Gerleman
f216c84175 [local] Fix workflow syntax 2023-06-29 11:34:23 -07:00
Nick Gerleman
b5d105132e [local] Set version to 2.0.0-beta.1 2023-06-29 11:22:56 -07:00
618 changed files with 45742 additions and 148113 deletions

View File

@@ -1,3 +1,61 @@
Language: Cpp
AccessModifierOffset: -2
AlignAfterOpenBracket: AlwaysBreak
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: false
AlignTrailingComments: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BinPackParameters: false
BreakBeforeBinaryOperators: false
BreakBeforeBraces: Attach
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
BreakStringLiterals: true
ColumnLimit: 80
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
FixNamespaceComments: true
IndentCaseLabels: true
IndentPPDirectives: None
IndentWidth: 2
IndentWrappedFunctionNames: false
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
PenaltyReturnTypeOnItsOwnLine: 2000
PointerAlignment: Left
ReflowComments: true
SortIncludes: false
SortUsingDeclarations: true
SpaceAfterCStyleCast: true
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
Standard: Cpp11
UseTab: Never
---
Language: ObjC
AccessModifierOffset: -1
AlignAfterOpenBracket: AlwaysBreak
AlignConsecutiveMacros: false
@@ -107,3 +165,4 @@ Standard: Latest
TabWidth: 8
UseCRLF: false
UseTab: Never
...

1
.clang-format-ignore Normal file
View File

@@ -0,0 +1 @@
^lib/.*

View File

@@ -1,217 +0,0 @@
---
InheritParentConfig: true
Checks: '>
bugprone-argument-comment,
bugprone-assert-side-effect,
bugprone-bool-pointer-implicit-conversion,
bugprone-copy-constructor-init,
bugprone-dangling-handle,
bugprone-exception-escape,
bugprone-fold-init-type,
bugprone-forward-declaration-namespace,
bugprone-forwarding-reference-overload,
bugprone-incorrect-enable-if,
bugprone-inaccurate-erase,
bugprone-incorrect-roundings,
bugprone-infinite-loop,
bugprone-integer-division,
bugprone-macro-parentheses,
bugprone-macro-repeated-side-effects,
bugprone-misplaced-operator-in-strlen-in-alloc,
bugprone-misplaced-widening-cast,
bugprone-move-forwarding-reference,
bugprone-multiple-statement-macro,
bugprone-optional-value-conversion,
bugprone-parent-virtual-call,
bugprone-redundant-branch-condition,
bugprone-shared-ptr-array-mismatch,
bugprone-signed-char-misuse,
bugprone-sizeof-container,
bugprone-sizeof-expression,
bugprone-string-constructor,
bugprone-string-integer-assignment,
bugprone-string-literal-with-embedded-nul,
bugprone-suspicious-enum-usage,
bugprone-suspicious-memset-usage,
bugprone-suspicious-missing-comma,
bugprone-suspicious-semicolon,
bugprone-suspicious-string-compare,
bugprone-swapped-arguments,
bugprone-terminating-continue,
bugprone-throw-keyword-missing,
bugprone-too-small-loop-variable,
bugprone-undefined-memory-manipulation,
bugprone-undelegated-constructor,
bugprone-unique-ptr-array-mismatch,
bugprone-unsafe-functions,
bugprone-unused-raii,
bugprone-unused-return-value,
bugprone-use-after-move,
bugprone-virtual-near-miss,
clang-analyzer-apiModeling.google.GTest,
clang-analyzer-apiModeling.StdCLibraryFunctions,
clang-analyzer-apiModeling.TrustNonnull,
clang-analyzer-core.builtin.BuiltinFunctions,
clang-analyzer-core.builtin.NoReturnFunctions,
clang-analyzer-core.CallAndMessage,
clang-analyzer-core.DivideZero,
clang-analyzer-core.DynamicTypePropagation,
clang-analyzer-core.NonnilStringConstants,
clang-analyzer-core.NonNullParamChecker,
clang-analyzer-core.NullDereference,
clang-analyzer-core.StackAddressEscape,
clang-analyzer-core.UndefinedBinaryOperatorResult,
clang-analyzer-core.uninitialized.ArraySubscript,
clang-analyzer-core.uninitialized.Assign,
clang-analyzer-core.uninitialized.Branch,
clang-analyzer-core.uninitialized.CapturedBlockVariable,
clang-analyzer-core.uninitialized.UndefReturn,
clang-analyzer-core.VLASize,
clang-analyzer-cplusplus.InnerPointer,
clang-analyzer-cplusplus.Move,
clang-analyzer-cplusplus.NewDelete,
clang-analyzer-cplusplus.NewDeleteLeaks,
clang-analyzer-cplusplus.SelfAssignment,
clang-analyzer-deadcode.DeadStores,
clang-analyzer-nullability.NullableDereferenced,
clang-analyzer-nullability.NullablePassedToNonnull,
clang-analyzer-nullability.NullableReturnedFromNonnull,
clang-analyzer-nullability.NullPassedToNonnull,
clang-analyzer-nullability.NullReturnedFromNonnull,
clang-analyzer-optin.cplusplus.VirtualCall,
clang-analyzer-optin.mpi.MPI-Checker,
clang-analyzer-optin.performance.GCDAntipattern,
clang-analyzer-optin.performance.Padding,
clang-analyzer-optin.portability.UnixAPI,
clang-analyzer-security.FloatLoopCounter,
clang-analyzer-security.insecureAPI.bcmp,
clang-analyzer-security.insecureAPI.bcopy,
clang-analyzer-security.insecureAPI.bzero,
clang-analyzer-security.insecureAPI.getpw,
clang-analyzer-security.insecureAPI.gets,
clang-analyzer-security.insecureAPI.mkstemp,
clang-analyzer-security.insecureAPI.mktemp,
clang-analyzer-security.insecureAPI.rand,
clang-analyzer-security.insecureAPI.strcpy,
clang-analyzer-security.insecureAPI.UncheckedReturn,
clang-analyzer-security.insecureAPI.vfork,
clang-analyzer-unix.API,
clang-analyzer-unix.cstring.BadSizeArg,
clang-analyzer-unix.cstring.NullArg,
clang-analyzer-unix.Malloc,
clang-analyzer-unix.MallocSizeof,
clang-analyzer-unix.MismatchedDeallocator,
clang-analyzer-unix.Vfork,
clang-analyzer-valist.CopyToSelf,
clang-analyzer-valist.Uninitialized,
clang-analyzer-valist.Unterminated,
clang-diagnostic-*,
cppcoreguidelines-avoid-const-or-ref-data-members,
cppcoreguidelines-avoid-non-const-global-variables,
cppcoreguidelines-init-variables,
cppcoreguidelines-interfaces-global-init,
cppcoreguidelines-macro-usage,
cppcoreguidelines-missing-std-forward,
cppcoreguidelines-narrowing-conversions,
cppcoreguidelines-no-malloc,
cppcoreguidelines-prefer-member-initializer,
cppcoreguidelines-pro-bounds-pointer-arithmetic,
cppcoreguidelines-pro-type-const-cast,
cppcoreguidelines-pro-type-cstyle-cast,
cppcoreguidelines-pro-type-member-init,
cppcoreguidelines-pro-type-reinterpret-cast,
cppcoreguidelines-pro-type-union-access,
cppcoreguidelines-pro-type-vararg,
cppcoreguidelines-slicing,
cppcoreguidelines-special-member-functions,
facebook-hte-BadEnum,
facebook-hte-MissingStatic,
google-build-using-namespace,
misc-definitions-in-headers,
misc-header-include-cycle,
misc-misplaced-const,
misc-new-delete-overloads,
misc-non-copyable-objects,
misc-static-assert,
misc-throw-by-value-catch-by-reference,
misc-unconventional-assign-operator,
misc-uniqueptr-reset-release,
misc-unused-alias-decls,
misc-unused-parameters,
misc-unused-using-decls,
modernize-avoid-bind,
modernize-avoid-c-arrays,
modernize-concat-nested-namespaces,
modernize-deprecated-headers,
modernize-deprecated-ios-base-aliases,
modernize-loop-convert,
modernize-make-shared,
modernize-make-unique,
modernize-pass-by-value,
modernize-raw-string-literal,
modernize-redundant-void-arg,
modernize-replace-auto-ptr,
modernize-replace-random-shuffle,
modernize-return-braced-init-list,
modernize-shrink-to-fit,
modernize-unary-static-assert,
modernize-use-auto,
modernize-use-bool-literals,
modernize-use-constraints,
modernize-use-default-member-init,
modernize-use-designated-initializers,
modernize-use-emplace,
modernize-use-equals-default,
modernize-use-equals-delete,
modernize-use-noexcept,
modernize-use-nullptr,
modernize-use-override,
modernize-use-starts-ends-with,
modernize-use-transparent-functors,
modernize-use-using,
performance-faster-string-find,
performance-for-range-copy,
performance-implicit-conversion-in-loop,
performance-inefficient-algorithm,
performance-inefficient-string-concatenation,
performance-inefficient-vector-operation,
performance-move-const-arg,
performance-move-constructor-init,
performance-noexcept-move-constructor,
performance-type-promotion-in-math-fn,
performance-unnecessary-copy-initialization,
performance-unnecessary-value-param,
readability-avoid-const-params-in-decls,
readability-braces-around-statements,
readability-const-return-type,
readability-container-size-empty,
readability-delete-null-pointer,
readability-deleted-default,
readability-implicit-bool-conversion,
readability-inconsistent-declaration-parameter-name,
readability-isolate-declaration,
readability-misplaced-array-index,
readability-named-parameter,
readability-non-const-parameter,
readability-redundant-control-flow,
readability-redundant-declaration,
readability-redundant-function-ptr-dereference,
readability-redundant-preprocessor,
readability-redundant-smartptr-get,
readability-redundant-string-cstr,
readability-redundant-string-init,
readability-simplify-boolean-expr,
readability-simplify-subscript-expr,
readability-static-accessed-through-instance,
readability-static-definition-in-anonymous-namespace,
readability-string-compare,
readability-uniqueptr-delete-release,
'
CheckOptions:
- key: performance-unnecessary-value-param.AllowedTypes
value: '[Pp]ointer$;[Pp]tr$;[Rr]ef(erence)?$;'
- key: performance-unnecessary-copy-initialization.AllowedTypes
value: '[Pp]ointer$;[Pp]tr$;[Rr]ef(erence)?$'
...

View File

@@ -1,84 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
module.exports = {
root: true,
ignorePatterns: ['**/binaries/**', '**/build/**', '**/generated/**'],
overrides: [
// Catch-all
{
files: ['**/*.?(m|c){j,t}s?(x)'],
extends: ['eslint:recommended', 'plugin:prettier/recommended'],
plugins: ['prettier'],
rules: {
'no-unused-vars': ['error', {argsIgnorePattern: '^_'}],
'no-var': 'error',
'prefer-arrow-callback': 'error',
'prefer-const': 'error',
'prefer-object-spread': 'error',
'prefer-spread': 'error',
'require-await': 'error',
},
env: {
es2020: true,
'shared-node-browser': true,
},
parserOptions: {
ecmaVersion: 'latest',
},
},
// ES Modules
{
files: ['**/*.?(m){j,t}s?(x)'],
parserOptions: {
sourceType: 'module',
},
},
// CommonJS Modules
{
files: ['**/*.c{j,t}s?(x)'],
env: {
commonjs: true,
},
parserOptions: {
sourceType: 'commonjs',
},
},
// TypeScript
{
files: ['**/*.?(m|c)ts?(x)'],
extends: ['plugin:@typescript-eslint/recommended'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: true,
},
plugins: ['@typescript-eslint'],
rules: {
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{argsIgnorePattern: '^_'},
],
'@typescript-eslint/no-var-requires': 'off',
},
},
// Node
{
files: ['**/.*rc.(c){j,t}s', '**/*.config.?(c){j,t}s'],
env: {
node: true,
},
},
// Jest
{
files: ['**/tests/**'],
extends: ['plugin:jest/recommended'],
},
],
};

View File

@@ -1,10 +0,0 @@
name: Cache the installed copy of emsdk and its build artifacts
runs:
using: "composite"
steps:
- name: Cache emsdk
uses: actions/cache@v3
with:
path: javascript/.emsdk
key: emsdk-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('yarn.lock', 'javascript/package.json', 'javascript/just.config.cjs')}}

View File

@@ -0,0 +1,23 @@
name: Install emsdk (including emcc)
inputs:
version:
description: EMCC Version to install
required: false
default: 3.1.28
runs:
using: "composite"
steps:
- name: Clone emsdk repo
working-directory: ${{ runner.temp }}
shell: bash
run: git clone https://github.com/emscripten-core/emsdk.git
- name: emdsk install
working-directory: ${{ runner.temp }}/emsdk
shell: bash
run: |
./emsdk install ${{ inputs.version }}
./emsdk activate ${{ inputs.version }}
echo $RUNNER_TEMP/emsdk >> $GITHUB_PATH
echo $RUNNER_TEMP/emsdk/upstream/emscripten >> $GITHUB_PATH

View File

@@ -7,7 +7,3 @@ runs:
- name: Install Cocoapods
shell: bash
run: sudo gem install cocoapods
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: 14.3.1

View File

@@ -1,9 +1,4 @@
name: Setup C++ envirionment
inputs:
toolchain:
description: Compiler toolchain to use (Clang, GCC, or MSVC)
required: false
default: 'Clang'
runs:
using: "composite"
@@ -12,22 +7,6 @@ runs:
if: ${{ runner.os != 'Windows' }}
uses: ./.github/actions/install-ninja
- name: Set Clang as compiler
if: ${{ inputs.toolchain == 'Clang' }}
shell: bash
run: |
echo "CC=/usr/bin/clang" >> $GITHUB_ENV
echo "CXX=/usr/bin/clang++" >> $GITHUB_ENV
echo "CXXFLAGS=-stdlib=libc++" >> $GITHUB_ENV
echo "LDFLAGS=-stdlib=libc++" >> $GITHUB_ENV
- name: Set GCC as compiler
if: ${{ inputs.toolchain == 'GCC' }}
shell: bash
run: |
echo "CC=/usr/bin/gcc" >> $GITHUB_ENV
echo "CXX=/usr/bin/g++" >> $GITHUB_ENV
- name: Setup VS Developer Command Prompt
if: ${{ runner.os == 'Windows' }}
uses: ilammy/msvc-dev-cmd@v1

View File

@@ -3,16 +3,18 @@ name: Setup JavaScript envirionment
runs:
using: "composite"
steps:
- name: Setup Node environment
uses: actions/setup-node@v3
- name: Install Node
uses: actions/setup-node@v1
with:
node-version: 20.x
cache: yarn
cache-dependency-path: yarn.lock
env:
# https://github.com/actions/setup-node/issues/317
FORCE_COLOR: 0
node-version: 18.x
- name: Install emsdk
uses: ./.github/actions/install-emsdk
- name: Install Ninja
uses: ./.github/actions/install-ninja
- name: yarn install
shell: bash
run: yarn install --frozen-lockfile
working-directory: javascript

View File

@@ -0,0 +1,18 @@
name: Setup Website envirionment
runs:
using: "composite"
steps:
# TODO: Update to latest when website is moved to the workspace version of
# yoga-layout
- name: Install Node
uses: actions/setup-node@v1
with:
node-version: 12.x
# TODO: the website should be in a yarn workspace with the library, but the
# current version of gatsby is incompatible with hoisting.
- name: yarn install
shell: bash
run: yarn install --frozen-lockfile
working-directory: website

View File

@@ -9,7 +9,7 @@ on:
jobs:
publish:
name: Publish to CocoaPods trunk
runs-on: macos-13
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
@@ -21,3 +21,9 @@ jobs:
run: pod trunk push Yoga.podspec
env:
COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
- name: Publish YogaKit
# Must run with --synchronous since YogaKit may depend on the just published version of Yoga
run: pod trunk push YogaKit.podspec --synchronous
env:
COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}

32
.github/workflows/publish-website.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Website
on:
push:
branches:
- main
workflow_dispatch:
jobs:
publish:
name: Publish to GitHub Pages
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup-website
- name: yarn build
run: yarn build
working-directory: website
- uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_branch: gh-pages
publish_dir: website/public
cname: yogalayout.com
keep_files: true
user_name: 'Yoga-bot'
user_email: 'yogabot@fb.com'

View File

@@ -1,11 +1,10 @@
name: Validate Android
name: Android
on:
pull_request:
push:
branches:
- main
- 'release-*'
workflow_dispatch:
jobs:

View File

@@ -1,17 +1,16 @@
name: Validate Apple
name: Apple
on:
pull_request:
push:
branches:
- main
- 'release-*'
workflow_dispatch:
jobs:
lint-pods:
name: Build [CocoaPods]
runs-on: macos-13
name: Lint pod
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
@@ -22,9 +21,12 @@ jobs:
- name: pod lib lint
run: pod lib lint --verbose --include-podspecs=**/*.podspec
test:
name: Build [SwiftPM]
runs-on: macos-13
build-sample:
name: Build sample [${{ matrix.mode }}]
runs-on: macos-latest
strategy:
matrix:
mode: [Debug, Release]
steps:
- uses: actions/checkout@v3
@@ -32,8 +34,22 @@ jobs:
- name: Setup
uses: ./.github/actions/setup-apple
- name: Build Debug
run: swift build -c debug
- name: pod install
run: pod install
working-directory: ./YogaKit/YogaKitSample
- name: Build Release
run: swift build -c release
- name: xcodebuild YogaKitSample.xcworkspace
run: xcodebuild -workspace YogaKitSample.xcworkspace -scheme YogaKitSample
working-directory: ./YogaKit/YogaKitSample
clang-format:
name: Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: clang-format
uses: ./.github/actions/clang-format
with:
directory: ./YogaKit

View File

@@ -1,11 +1,10 @@
name: Validate C++
name: C++
on:
pull_request:
push:
branches:
- main
- 'release-*'
workflow_dispatch:
env:
@@ -13,91 +12,45 @@ env:
jobs:
test:
name: Build and Test [${{ matrix.toolchain }}][${{ matrix.mode }}]
runs-on: ${{ (matrix.toolchain == 'MSVC') && 'windows-latest' || 'ubuntu-latest' }}
name: Build and Test [${{ matrix.os }}][${{ matrix.mode }}]
runs-on: ${{ matrix.os }}
strategy:
matrix:
mode: [Debug, Release]
toolchain: [Clang, GCC] # TODO: fix issues building GTest Binary with MSVC in GitHub Actions
os: [ubuntu-latest] # TODO: fix issues building GTest Binary with MSVC in GitHub Actions
steps:
- uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup-cpp
with:
toolchain: ${{ matrix.toolchain }}
- name: Unit tests
run: ./unit_tests ${{ matrix.mode }}
build_fuzzers:
name: Build fuzzers [${{ matrix.toolchain }}][${{ matrix.mode }}]
runs-on: ubuntu-latest
strategy:
matrix:
mode: [Debug, Release]
toolchain: [Clang]
steps:
- uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup-cpp
with:
toolchain: ${{ matrix.toolchain }}
- name: Unit tests
run: ./build_fuzz_tests ${{ matrix.mode }}
benchmark:
name: Benchmark [${{ matrix.toolchain }}]
runs-on: ${{ (matrix.toolchain == 'MSVC') && 'windows-latest' || 'ubuntu-latest' }}
name: Benchmark [${{ matrix.os }}]
runs-on: ${{ matrix.os }}
strategy:
matrix:
toolchain: [Clang, GCC, MSVC]
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup-cpp
with:
toolchain: ${{ matrix.toolchain }}
- name: Build and run benchmark
- name: Build benchmark
run: |
cmake -S . -B build -G Ninja -D CMAKE_BUILD_TYPE=Release
cmake --build build
./build/benchmark ./captures
working-directory: benchmark
- name: Build and run benchmarklegacy
run: ./build/benchmarklegacy
- name: Run benchmark
run: ./build/benchmark
working-directory: benchmark
capture:
name: Capture [${{ matrix.toolchain }}]
runs-on: ${{ (matrix.toolchain == 'MSVC') && 'windows-latest' || 'ubuntu-latest' }}
strategy:
matrix:
toolchain: [Clang, GCC, MSVC]
steps:
- uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup-cpp
with:
toolchain: ${{ matrix.toolchain }}
- name: Build capture
run: |
cmake -S . -B build -G Ninja -D CMAKE_BUILD_TYPE=Release
cmake --build build
working-directory: capture
clang-format:
name: Format
runs-on: ubuntu-latest
@@ -107,3 +60,5 @@ jobs:
- name: clang-format
uses: ./.github/actions/clang-format
with:
directory: ./yoga

View File

@@ -1,11 +1,10 @@
name: Validate JavaScript
name: JavaScript
on:
pull_request:
push:
branches:
- main
- 'release-*'
workflow_dispatch:
env:
@@ -24,9 +23,6 @@ jobs:
- name: Setup
uses: ./.github/actions/setup-js
- name: Restore emsdk
uses: ./.github/actions/cache-emsdk
- name: yarn benchmark
run: yarn benchmark
working-directory: javascript
@@ -43,11 +39,8 @@ jobs:
- name: Setup
uses: ./.github/actions/setup-js
- name: Restore emsdk
uses: ./.github/actions/cache-emsdk
- name: yarn build
run: yarn build --verbose
run: yarn build
working-directory: javascript
test:
@@ -62,36 +55,22 @@ jobs:
- name: Setup
uses: ./.github/actions/setup-js
- name: Restore emsdk
uses: ./.github/actions/cache-emsdk
- name: yarn test
run: yarn test
working-directory: javascript
lint:
name: ESLint (All Packages)
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup-js
- name: yarn install
run: yarn install --frozen-lockfile
working-directory: javascript
- name: yarn lint
run: yarn lint
typecheck:
name: Typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup-js
- name: yarn tsc
run: yarn tsc
working-directory: javascript
pack:
@@ -103,9 +82,6 @@ jobs:
- name: Setup
uses: ./.github/actions/setup-js
- name: Restore emsdk
uses: ./.github/actions/cache-emsdk
- name: yarn pack
run: yarn pack --filename yoga-layout.tar.gz
working-directory: javascript

21
.github/workflows/validate-python.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Python
on:
pull_request:
push:
branches:
- main
workflow_dispatch:
jobs:
format:
name: Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: black --check
uses: ./.github/actions/black
with:
directory: ${{ github.workspace }}

View File

@@ -1,32 +0,0 @@
name: Validate Tests
on:
pull_request:
push:
branches:
- main
workflow_dispatch:
jobs:
validate:
name: Validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup JS
uses: ./.github/actions/setup-js
- name: yarn gentest-validate
run: yarn gentest-validate
- name: yarn gentest
run: yarn gentest -h
- name: Check for modified tests
run: |
if [[ -n $(git status -s) ]]; then
git status -s
echo "yarn gentest modified these tests. Please run yarn gentest to resolve."
exit 1
fi

View File

@@ -1,42 +1,23 @@
name: Validate Website
name: Website
on:
pull_request:
branches:
- main
push:
branches:
- main
workflow_dispatch:
jobs:
build_next:
build:
name: Build
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup-js
uses: ./.github/actions/setup-website
- name: Restore emsdk
uses: ./.github/actions/cache-emsdk
- name: Build Website
- name: yarn build
run: yarn build
working-directory: website
typecheck:
name: Typecheck [Docusaurus]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup
uses: ./.github/actions/setup-js
- name: yarn tsc
run: yarn tsc
working-directory: website

1
.gitignore vendored
View File

@@ -6,7 +6,6 @@
/.buckd
/gentest/test.html
.buck-java11
node_modules
# Jekyll
/.sass-cache/

View File

@@ -1,3 +0,0 @@
**/binaries/**
**/build/**
**/generated/**

View File

@@ -12,12 +12,6 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/project-defaults.cmake)
add_subdirectory(yoga)
add_subdirectory(tests)
option(BUILD_FUZZ_TESTS "Build fuzz tests" OFF)
if ('${CMAKE_CXX_COMPILER_ID}' MATCHES 'Clang' AND BUILD_FUZZ_TESTS)
add_subdirectory(fuzz)
endif()
# cmake install config
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)

View File

@@ -1,32 +0,0 @@
// swift-tools-version:5.0
// The swift-tools-version declares the minimum version of Swift required to build this package.
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import PackageDescription
let package = Package(
name: "yoga",
products: [
.library(name: "yoga", targets: [ "core" ])
],
targets: [
.target(
name: "core",
path: ".",
sources: [
"yoga"
],
publicHeadersPath: ".",
cxxSettings: [
.headerSearchPath(".")
]
)
],
cxxLanguageStandard: CXXLanguageStandard(rawValue: "c++20")
)

View File

@@ -1,10 +1,10 @@
# Yoga [![Support Ukraine](https://img.shields.io/badge/Support-Ukraine-FFD500?style=flat&labelColor=005BBB)](https://opensource.fb.com/support-ukraine) [![CocoaPods](https://img.shields.io/cocoapods/v/Yoga.svg)](http://cocoapods.org/pods/Yoga) [![npm](https://img.shields.io/npm/v/yoga-layout.svg)](https://www.npmjs.com/package/yoga-layout) [![Maven Central](https://img.shields.io/maven-central/v/com.facebook.yoga/yoga)](https://search.maven.org/artifact/com.facebook.yoga/yoga)
# Yoga [![Support Ukraine](https://img.shields.io/badge/Support-Ukraine-FFD500?style=flat&labelColor=005BBB)](https://opensource.fb.com/support-ukraine) [![CocoaPods](https://img.shields.io/cocoapods/v/YogaKit.svg)](http://cocoapods.org/pods/YogaKit) [![npm](https://img.shields.io/npm/v/yoga-layout.svg)](https://www.npmjs.com/package/yoga-layout) [![Maven Central](https://img.shields.io/maven-central/v/com.facebook.yoga/yoga)](https://search.maven.org/artifact/com.facebook.yoga/yoga)
Yoga is an embeddable and performant flexbox layout engine with bindings for multiple languages.
## Building
Yoga's main implementation targets C++ 20 with accompanying build logic in CMake. A wrapper is provided to build the main library and run unit tests.
## Building
Yoga's main implementation targets C++ 14 with accompanying build logic in CMake. A wrapper is provided to build the main library and run unit tests.
```sh
./unit_tests <Debug|Release>
@@ -14,6 +14,8 @@ While not required, this script will use [ninja](https://ninja-build.org/) if it
Yoga is additionally part of the [vcpkg](https://github.com/Microsoft/vcpkg/) collection of ports maintained by Microsoft and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository.
## Adding Tests
Many of Yoga's tests are automatically generated, using HTML fixtures describing node structure. These are rendered in Chrome to generate an expected layout result for the tree. New fixtures can be added to `gentest/fixtures`.
@@ -25,10 +27,9 @@ Many of Yoga's tests are automatically generated, using HTML fixtures describing
```
To generate new tests from added fixtures:
1. Run `bundle install` in the `gentest` directory to install dependencies of the test generator.
2. Run `ruby gentest.rb` in the `gentest` directory.
1. Ensure you have [yarn classic](https://classic.yarnpkg.com) installed.
2. Run `yarn install` to install dependencies for the test generator.
3. Run `yarn gentest` in the `yoga` directory.
## Debugging

View File

@@ -6,10 +6,10 @@
Pod::Spec.new do |spec|
spec.name = 'Yoga'
spec.version = '0.0.0'
spec.version = '2.0.1'
spec.license = { :type => 'MIT', :file => "LICENSE" }
spec.homepage = 'https://yogalayout.dev/'
spec.documentation_url = 'https://yogalayout.dev/docs'
spec.homepage = 'https://yogalayout.com/'
spec.documentation_url = 'https://yogalayout.com/docs'
spec.summary = 'An embeddable and performant flexbox layout engine with bindings for multiple languages'
@@ -24,27 +24,18 @@ Pod::Spec.new do |spec|
spec.module_name = 'yoga'
spec.requires_arc = false
spec.pod_target_xcconfig = {
'DEFINES_MODULE' => 'YES',
'HEADER_SEARCH_PATHS' => '"$(PODS_TARGET_SRCROOT)"',
'DEFINES_MODULE' => 'YES'
}
spec.compiler_flags = [
'-fno-omit-frame-pointer',
'-fexceptions',
'-Wall',
'-Werror',
'-std=c++20',
'-Wextra',
'-std=c++14',
'-fPIC'
]
spec.swift_version = '5.1'
spec.source_files = 'yoga/**/*.{h,cpp}'
spec.header_mappings_dir = 'yoga'
public_header_files = 'yoga/*.h'
spec.public_header_files = public_header_files
all_header_files = 'yoga/**/*.h'
spec.private_header_files = Dir.glob(all_header_files) - Dir.glob(public_header_files)
spec.preserve_paths = [all_header_files]
spec.public_header_files = 'yoga/{Yoga,YGEnums,YGMacros,YGValue}.h'
spec.swift_version = '5.1'
end

34
YogaKit.podspec Normal file
View File

@@ -0,0 +1,34 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
podspec = Pod::Spec.new do |spec|
spec.name = 'YogaKit'
spec.version = '2.0.1'
spec.license = { :type => 'MIT', :file => "LICENSE" }
spec.homepage = 'https://yogalayout.com/'
spec.documentation_url = 'https://yogalayout.com/docs'
spec.deprecated = true
spec.summary = 'YogaKit allows using the Yoga layout engine in combination with UIKit'
spec.authors = {'Meta Open Source' => 'opensource@meta.com'}
spec.source = {
:git => 'https://github.com/facebook/yoga.git',
:tag => "v#{spec.version.to_s}",
}
spec.platforms = { :ios => "13.4" }
spec.ios.frameworks = 'UIKit'
spec.module_name = 'YogaKit'
spec.dependency 'Yoga', "~> #{spec.version.to_s}"
spec.source_files = 'YogaKit/Source/*.{h,m,swift}'
spec.public_header_files = 'YogaKit/Source/{YGLayout,UIView+Yoga}.h'
spec.private_header_files = 'YogaKit/Source/YGLayout+Private.h'
spec.swift_version = '5.1'
end
# See https://github.com/facebook/yoga/pull/366
podspec.attributes_hash["readme"] = "YogaKit/README.md"
podspec

21
YogaKit/README.md Normal file
View File

@@ -0,0 +1,21 @@
# YogaKit
[![CocoaPods](https://img.shields.io/cocoapods/v/YogaKit.svg?style=flat)](https://cocoapods.org/pods/YogaKit)
[![Platform](https://img.shields.io/badge/platforms-iOS-orange.svg)](https://facebook.github.io/yoga/docs/api/yogakit/)
[![Languages](https://img.shields.io/badge/languages-ObjC%20%7C%20Swift-orange.svg)](https://facebook.github.io/yoga/docs/api/yogakit/)
## Installation
YogaKit is available to install via [CocoaPods](https://cocoapods.org/).
```
pod 'YogaKit', '~> 1.7'
```
## Getting Started
We have a sample project. To try it out, clone this repo and open `YogaKitSample.xcodeproj` in the [YogaKitSample](https://github.com/facebook/yoga/tree/main/YogaKit/YogaKitSample) directory.
## Contributing
We welcome all pull-requests! At Facebook we sync the open source version of `YogaKit` daily, so we're always testing the latest changes.
See the [CONTRIBUTING.md](https://github.com/facebook/yoga/blob/main/CONTRIBUTING.md) file for how to help out.

View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import "YGLayout.h"
NS_ASSUME_NONNULL_BEGIN
typedef void (^YGLayoutConfigurationBlock)(YGLayout* layout);
@interface UIView (Yoga)
/**
The YGLayout that is attached to this view. It is lazily created.
*/
@property(nonatomic, readonly, strong) YGLayout* yoga;
/**
Indicates whether or not Yoga is enabled
*/
@property(nonatomic, readonly, assign) BOOL isYogaEnabled;
/**
In ObjC land, every time you access `view.yoga.*` you are adding another
`objc_msgSend` to your code. If you plan on making multiple changes to
YGLayout, it's more performant to use this method, which uses a single
objc_msgSend call.
*/
- (void)configureLayoutWithBlock:(YGLayoutConfigurationBlock)block
NS_SWIFT_NAME(configureLayout(block:));
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <objc/runtime.h>
#import "UIView+Yoga.h"
#import "YGLayout+Private.h"
static const void* kYGYogaAssociatedKey = &kYGYogaAssociatedKey;
@implementation UIView (YogaKit)
- (YGLayout*)yoga {
YGLayout* yoga = objc_getAssociatedObject(self, kYGYogaAssociatedKey);
if (!yoga) {
yoga = [[YGLayout alloc] initWithView:self];
objc_setAssociatedObject(
self, kYGYogaAssociatedKey, yoga, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return yoga;
}
- (BOOL)isYogaEnabled {
return objc_getAssociatedObject(self, kYGYogaAssociatedKey) != nil;
}
- (void)configureLayoutWithBlock:(YGLayoutConfigurationBlock)block {
if (block != nil) {
block(self.yoga);
}
}
@end

View File

@@ -0,0 +1,17 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <yoga/Yoga.h>
#import "YGLayout.h"
@interface YGLayout ()
@property(nonatomic, assign, readonly) YGNodeRef node;
- (instancetype)initWithView:(UIView*)view;
@end

174
YogaKit/Source/YGLayout.h Normal file
View File

@@ -0,0 +1,174 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <yoga/YGEnums.h>
#import <yoga/YGMacros.h>
#import <yoga/Yoga.h>
YG_EXTERN_C_BEGIN
extern YGValue YGPointValue(CGFloat value) NS_SWIFT_UNAVAILABLE(
"Use the swift Int and FloatingPoint extensions instead");
extern YGValue YGPercentValue(CGFloat value) NS_SWIFT_UNAVAILABLE(
"Use the swift Int and FloatingPoint extensions instead");
YG_EXTERN_C_END
typedef NS_OPTIONS(NSInteger, YGDimensionFlexibility) {
YGDimensionFlexibilityFlexibleWidth = 1 << 0,
YGDimensionFlexibilityFlexibleHeight = 1 << 1,
};
@interface YGLayout : NSObject
/**
Make default init unavailable, as it will not initialise YGNode which is
required for the setters and getters of YGLayout's properties to work properly.
*/
- (instancetype)init
__attribute__((unavailable("you are not meant to initialise YGLayout")));
/**
Make default init unavailable, as it will not initialise YGNode which is
required for the setters and getters of YGLayout's properties to work properly.
*/
+ (instancetype)new
__attribute__((unavailable("you are not meant to initialise YGLayout")));
/**
The property that decides if we should include this view when calculating
layout. Defaults to YES.
*/
@property(nonatomic, readwrite, assign, setter=setIncludedInLayout:)
BOOL isIncludedInLayout;
/**
The property that decides during layout/sizing whether or not styling
properties should be applied. Defaults to NO.
*/
@property(nonatomic, readwrite, assign, setter=setEnabled:) BOOL isEnabled;
@property(nonatomic, readwrite, assign) YGDirection direction;
@property(nonatomic, readwrite, assign) YGFlexDirection flexDirection;
@property(nonatomic, readwrite, assign) YGJustify justifyContent;
@property(nonatomic, readwrite, assign) YGAlign alignContent;
@property(nonatomic, readwrite, assign) YGAlign alignItems;
@property(nonatomic, readwrite, assign) YGAlign alignSelf;
@property(nonatomic, readwrite, assign) YGPositionType position;
@property(nonatomic, readwrite, assign) YGWrap flexWrap;
@property(nonatomic, readwrite, assign) YGOverflow overflow;
@property(nonatomic, readwrite, assign) YGDisplay display;
@property(nonatomic, readwrite, assign) CGFloat flex;
@property(nonatomic, readwrite, assign) CGFloat flexGrow;
@property(nonatomic, readwrite, assign) CGFloat flexShrink;
@property(nonatomic, readwrite, assign) YGValue flexBasis;
@property(nonatomic, readwrite, assign) YGValue left;
@property(nonatomic, readwrite, assign) YGValue top;
@property(nonatomic, readwrite, assign) YGValue right;
@property(nonatomic, readwrite, assign) YGValue bottom;
@property(nonatomic, readwrite, assign) YGValue start;
@property(nonatomic, readwrite, assign) YGValue end;
@property(nonatomic, readwrite, assign) YGValue marginLeft;
@property(nonatomic, readwrite, assign) YGValue marginTop;
@property(nonatomic, readwrite, assign) YGValue marginRight;
@property(nonatomic, readwrite, assign) YGValue marginBottom;
@property(nonatomic, readwrite, assign) YGValue marginStart;
@property(nonatomic, readwrite, assign) YGValue marginEnd;
@property(nonatomic, readwrite, assign) YGValue marginHorizontal;
@property(nonatomic, readwrite, assign) YGValue marginVertical;
@property(nonatomic, readwrite, assign) YGValue margin;
@property(nonatomic, readwrite, assign) YGValue paddingLeft;
@property(nonatomic, readwrite, assign) YGValue paddingTop;
@property(nonatomic, readwrite, assign) YGValue paddingRight;
@property(nonatomic, readwrite, assign) YGValue paddingBottom;
@property(nonatomic, readwrite, assign) YGValue paddingStart;
@property(nonatomic, readwrite, assign) YGValue paddingEnd;
@property(nonatomic, readwrite, assign) YGValue paddingHorizontal;
@property(nonatomic, readwrite, assign) YGValue paddingVertical;
@property(nonatomic, readwrite, assign) YGValue padding;
@property(nonatomic, readwrite, assign) CGFloat borderLeftWidth;
@property(nonatomic, readwrite, assign) CGFloat borderTopWidth;
@property(nonatomic, readwrite, assign) CGFloat borderRightWidth;
@property(nonatomic, readwrite, assign) CGFloat borderBottomWidth;
@property(nonatomic, readwrite, assign) CGFloat borderStartWidth;
@property(nonatomic, readwrite, assign) CGFloat borderEndWidth;
@property(nonatomic, readwrite, assign) CGFloat borderWidth;
@property(nonatomic, readwrite, assign) YGValue width;
@property(nonatomic, readwrite, assign) YGValue height;
@property(nonatomic, readwrite, assign) YGValue minWidth;
@property(nonatomic, readwrite, assign) YGValue minHeight;
@property(nonatomic, readwrite, assign) YGValue maxWidth;
@property(nonatomic, readwrite, assign) YGValue maxHeight;
// Yoga specific properties, not compatible with flexbox specification
@property(nonatomic, readwrite, assign) CGFloat aspectRatio;
/**
Get the resolved direction of this node. This won't be YGDirectionInherit
*/
@property(nonatomic, readonly, assign) YGDirection resolvedDirection;
/**
Perform a layout calculation and update the frames of the views in the
hierarchy with the results. If the origin is not preserved, the root view's
layout results will applied from {0,0}.
*/
- (void)applyLayoutPreservingOrigin:(BOOL)preserveOrigin
NS_SWIFT_NAME(applyLayout(preservingOrigin:));
/**
Perform a layout calculation and update the frames of the views in the
hierarchy with the results. If the origin is not preserved, the root view's
layout results will applied from {0,0}.
*/
- (void)applyLayoutPreservingOrigin:(BOOL)preserveOrigin
dimensionFlexibility:(YGDimensionFlexibility)dimensionFlexibility
NS_SWIFT_NAME(applyLayout(preservingOrigin:dimensionFlexibility:));
/**
Returns the size of the view if no constraints were given. This could
equivalent to calling [self sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)];
*/
@property(nonatomic, readonly, assign) CGSize intrinsicSize;
/**
Returns the size of the view based on provided constraints. Pass NaN for an
unconstrained dimension.
*/
- (CGSize)calculateLayoutWithSize:(CGSize)size
NS_SWIFT_NAME(calculateLayout(with:));
/**
Returns the number of children that are using Flexbox.
*/
@property(nonatomic, readonly, assign) NSUInteger numberOfChildren;
/**
Return a BOOL indiciating whether or not we this node contains any subviews
that are included in Yoga's layout.
*/
@property(nonatomic, readonly, assign) BOOL isLeaf;
/**
Return's a BOOL indicating if a view is dirty. When a node is dirty
it usually indicates that it will be remeasured on the next layout pass.
*/
@property(nonatomic, readonly, assign) BOOL isDirty;
/**
Mark that a view's layout needs to be recalculated. Only works for leaf views.
*/
- (void)markDirty;
@end

511
YogaKit/Source/YGLayout.m Normal file
View File

@@ -0,0 +1,511 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "UIView+Yoga.h"
#import "YGLayout+Private.h"
#define YG_PROPERTY(type, lowercased_name, capitalized_name) \
-(type)lowercased_name { \
return YGNodeStyleGet##capitalized_name(self.node); \
} \
\
-(void)set##capitalized_name : (type)lowercased_name { \
YGNodeStyleSet##capitalized_name(self.node, lowercased_name); \
}
#define YG_VALUE_PROPERTY(lowercased_name, capitalized_name) \
-(YGValue)lowercased_name { \
return YGNodeStyleGet##capitalized_name(self.node); \
} \
\
-(void)set##capitalized_name : (YGValue)lowercased_name { \
switch (lowercased_name.unit) { \
case YGUnitUndefined: \
YGNodeStyleSet##capitalized_name(self.node, lowercased_name.value); \
break; \
case YGUnitPoint: \
YGNodeStyleSet##capitalized_name(self.node, lowercased_name.value); \
break; \
case YGUnitPercent: \
YGNodeStyleSet##capitalized_name##Percent( \
self.node, lowercased_name.value); \
break; \
default: \
NSAssert(NO, @"Not implemented"); \
} \
}
#define YG_AUTO_VALUE_PROPERTY(lowercased_name, capitalized_name) \
-(YGValue)lowercased_name { \
return YGNodeStyleGet##capitalized_name(self.node); \
} \
\
-(void)set##capitalized_name : (YGValue)lowercased_name { \
switch (lowercased_name.unit) { \
case YGUnitPoint: \
YGNodeStyleSet##capitalized_name(self.node, lowercased_name.value); \
break; \
case YGUnitPercent: \
YGNodeStyleSet##capitalized_name##Percent( \
self.node, lowercased_name.value); \
break; \
case YGUnitAuto: \
YGNodeStyleSet##capitalized_name##Auto(self.node); \
break; \
default: \
NSAssert(NO, @"Not implemented"); \
} \
}
#define YG_EDGE_PROPERTY_GETTER( \
type, lowercased_name, capitalized_name, property, edge) \
-(type)lowercased_name { \
return YGNodeStyleGet##property(self.node, edge); \
}
#define YG_EDGE_PROPERTY_SETTER( \
lowercased_name, capitalized_name, property, edge) \
-(void)set##capitalized_name : (CGFloat)lowercased_name { \
YGNodeStyleSet##property(self.node, edge, lowercased_name); \
}
#define YG_EDGE_PROPERTY(lowercased_name, capitalized_name, property, edge) \
YG_EDGE_PROPERTY_GETTER( \
CGFloat, lowercased_name, capitalized_name, property, edge) \
YG_EDGE_PROPERTY_SETTER(lowercased_name, capitalized_name, property, edge)
#define YG_VALUE_EDGE_PROPERTY_SETTER( \
objc_lowercased_name, objc_capitalized_name, c_name, edge) \
-(void)set##objc_capitalized_name : (YGValue)objc_lowercased_name { \
switch (objc_lowercased_name.unit) { \
case YGUnitUndefined: \
YGNodeStyleSet##c_name(self.node, edge, objc_lowercased_name.value); \
break; \
case YGUnitPoint: \
YGNodeStyleSet##c_name(self.node, edge, objc_lowercased_name.value); \
break; \
case YGUnitPercent: \
YGNodeStyleSet##c_name##Percent( \
self.node, edge, objc_lowercased_name.value); \
break; \
default: \
NSAssert(NO, @"Not implemented"); \
} \
}
#define YG_VALUE_EDGE_PROPERTY( \
lowercased_name, capitalized_name, property, edge) \
YG_EDGE_PROPERTY_GETTER( \
YGValue, lowercased_name, capitalized_name, property, edge) \
YG_VALUE_EDGE_PROPERTY_SETTER( \
lowercased_name, capitalized_name, property, edge)
#define YG_VALUE_EDGES_PROPERTIES(lowercased_name, capitalized_name) \
YG_VALUE_EDGE_PROPERTY( \
lowercased_name##Left, \
capitalized_name##Left, \
capitalized_name, \
YGEdgeLeft) \
YG_VALUE_EDGE_PROPERTY( \
lowercased_name##Top, \
capitalized_name##Top, \
capitalized_name, \
YGEdgeTop) \
YG_VALUE_EDGE_PROPERTY( \
lowercased_name##Right, \
capitalized_name##Right, \
capitalized_name, \
YGEdgeRight) \
YG_VALUE_EDGE_PROPERTY( \
lowercased_name##Bottom, \
capitalized_name##Bottom, \
capitalized_name, \
YGEdgeBottom) \
YG_VALUE_EDGE_PROPERTY( \
lowercased_name##Start, \
capitalized_name##Start, \
capitalized_name, \
YGEdgeStart) \
YG_VALUE_EDGE_PROPERTY( \
lowercased_name##End, \
capitalized_name##End, \
capitalized_name, \
YGEdgeEnd) \
YG_VALUE_EDGE_PROPERTY( \
lowercased_name##Horizontal, \
capitalized_name##Horizontal, \
capitalized_name, \
YGEdgeHorizontal) \
YG_VALUE_EDGE_PROPERTY( \
lowercased_name##Vertical, \
capitalized_name##Vertical, \
capitalized_name, \
YGEdgeVertical) \
YG_VALUE_EDGE_PROPERTY( \
lowercased_name, capitalized_name, capitalized_name, YGEdgeAll)
YGValue YGPointValue(CGFloat value) {
return (YGValue){.value = value, .unit = YGUnitPoint};
}
YGValue YGPercentValue(CGFloat value) {
return (YGValue){.value = value, .unit = YGUnitPercent};
}
static YGConfigRef globalConfig;
@interface YGLayout ()
@property(nonatomic, weak, readonly) UIView* view;
@property(nonatomic, assign, readonly) BOOL isUIView;
@end
@implementation YGLayout
@synthesize isEnabled = _isEnabled;
@synthesize isIncludedInLayout = _isIncludedInLayout;
@synthesize node = _node;
+ (void)initialize {
globalConfig = YGConfigNew();
YGConfigSetExperimentalFeatureEnabled(
globalConfig, YGExperimentalFeatureWebFlexBasis, true);
YGConfigSetErrata(globalConfig, YGErrataClassic);
YGConfigSetPointScaleFactor(globalConfig, [UIScreen mainScreen].scale);
}
- (instancetype)initWithView:(UIView*)view {
if (self = [super init]) {
_view = view;
_node = YGNodeNewWithConfig(globalConfig);
YGNodeSetContext(_node, (__bridge void*)view);
_isEnabled = NO;
_isIncludedInLayout = YES;
_isUIView = [view isMemberOfClass:[UIView class]];
}
return self;
}
- (void)dealloc {
YGNodeFree(self.node);
}
- (BOOL)isDirty {
return YGNodeIsDirty(self.node);
}
- (void)markDirty {
if (self.isDirty || !self.isLeaf) {
return;
}
// Yoga is not happy if we try to mark a node as "dirty" before we have set
// the measure function. Since we already know that this is a leaf,
// this *should* be fine. Forgive me Hack Gods.
const YGNodeRef node = self.node;
if (!YGNodeHasMeasureFunc(node)) {
YGNodeSetMeasureFunc(node, YGMeasureView);
}
YGNodeMarkDirty(node);
}
- (NSUInteger)numberOfChildren {
return YGNodeGetChildCount(self.node);
}
- (BOOL)isLeaf {
NSAssert(
[NSThread isMainThread],
@"This method must be called on the main thread.");
if (self.isEnabled) {
for (UIView* subview in self.view.subviews) {
YGLayout* const yoga = subview.yoga;
if (yoga.isEnabled && yoga.isIncludedInLayout) {
return NO;
}
}
}
return YES;
}
#pragma mark - Style
- (YGPositionType)position {
return YGNodeStyleGetPositionType(self.node);
}
- (void)setPosition:(YGPositionType)position {
YGNodeStyleSetPositionType(self.node, position);
}
YG_PROPERTY(YGDirection, direction, Direction)
YG_PROPERTY(YGFlexDirection, flexDirection, FlexDirection)
YG_PROPERTY(YGJustify, justifyContent, JustifyContent)
YG_PROPERTY(YGAlign, alignContent, AlignContent)
YG_PROPERTY(YGAlign, alignItems, AlignItems)
YG_PROPERTY(YGAlign, alignSelf, AlignSelf)
YG_PROPERTY(YGWrap, flexWrap, FlexWrap)
YG_PROPERTY(YGOverflow, overflow, Overflow)
YG_PROPERTY(YGDisplay, display, Display)
YG_PROPERTY(CGFloat, flex, Flex)
YG_PROPERTY(CGFloat, flexGrow, FlexGrow)
YG_PROPERTY(CGFloat, flexShrink, FlexShrink)
YG_AUTO_VALUE_PROPERTY(flexBasis, FlexBasis)
YG_VALUE_EDGE_PROPERTY(left, Left, Position, YGEdgeLeft)
YG_VALUE_EDGE_PROPERTY(top, Top, Position, YGEdgeTop)
YG_VALUE_EDGE_PROPERTY(right, Right, Position, YGEdgeRight)
YG_VALUE_EDGE_PROPERTY(bottom, Bottom, Position, YGEdgeBottom)
YG_VALUE_EDGE_PROPERTY(start, Start, Position, YGEdgeStart)
YG_VALUE_EDGE_PROPERTY(end, End, Position, YGEdgeEnd)
YG_VALUE_EDGES_PROPERTIES(margin, Margin)
YG_VALUE_EDGES_PROPERTIES(padding, Padding)
YG_EDGE_PROPERTY(borderLeftWidth, BorderLeftWidth, Border, YGEdgeLeft)
YG_EDGE_PROPERTY(borderTopWidth, BorderTopWidth, Border, YGEdgeTop)
YG_EDGE_PROPERTY(borderRightWidth, BorderRightWidth, Border, YGEdgeRight)
YG_EDGE_PROPERTY(borderBottomWidth, BorderBottomWidth, Border, YGEdgeBottom)
YG_EDGE_PROPERTY(borderStartWidth, BorderStartWidth, Border, YGEdgeStart)
YG_EDGE_PROPERTY(borderEndWidth, BorderEndWidth, Border, YGEdgeEnd)
YG_EDGE_PROPERTY(borderWidth, BorderWidth, Border, YGEdgeAll)
YG_AUTO_VALUE_PROPERTY(width, Width)
YG_AUTO_VALUE_PROPERTY(height, Height)
YG_VALUE_PROPERTY(minWidth, MinWidth)
YG_VALUE_PROPERTY(minHeight, MinHeight)
YG_VALUE_PROPERTY(maxWidth, MaxWidth)
YG_VALUE_PROPERTY(maxHeight, MaxHeight)
YG_PROPERTY(CGFloat, aspectRatio, AspectRatio)
YG_EDGE_PROPERTY(columnGap, ColumnGap, Gap, YGGutterColumn)
YG_EDGE_PROPERTY(rowGap, RowGap, Gap, YGGutterRow)
YG_EDGE_PROPERTY(gap, Gap, Gap, YGGutterAll)
#pragma mark - Layout and Sizing
- (YGDirection)resolvedDirection {
return YGNodeLayoutGetDirection(self.node);
}
- (void)applyLayout {
[self calculateLayoutWithSize:self.view.bounds.size];
YGApplyLayoutToViewHierarchy(self.view, NO);
}
- (void)applyLayoutPreservingOrigin:(BOOL)preserveOrigin {
[self calculateLayoutWithSize:self.view.bounds.size];
YGApplyLayoutToViewHierarchy(self.view, preserveOrigin);
}
- (void)applyLayoutPreservingOrigin:(BOOL)preserveOrigin
dimensionFlexibility:
(YGDimensionFlexibility)dimensionFlexibility {
CGSize size = self.view.bounds.size;
if (dimensionFlexibility & YGDimensionFlexibilityFlexibleWidth) {
size.width = YGUndefined;
}
if (dimensionFlexibility & YGDimensionFlexibilityFlexibleHeight) {
size.height = YGUndefined;
}
[self calculateLayoutWithSize:size];
YGApplyLayoutToViewHierarchy(self.view, preserveOrigin);
}
- (CGSize)intrinsicSize {
const CGSize constrainedSize = {
.width = YGUndefined,
.height = YGUndefined,
};
return [self calculateLayoutWithSize:constrainedSize];
}
- (CGSize)calculateLayoutWithSize:(CGSize)size {
NSAssert([NSThread isMainThread], @"Yoga calculation must be done on main.");
NSAssert(self.isEnabled, @"Yoga is not enabled for this view.");
YGAttachNodesFromViewHierachy(self.view);
const YGNodeRef node = self.node;
YGNodeCalculateLayout(
node, size.width, size.height, YGNodeStyleGetDirection(node));
return (CGSize){
.width = YGNodeLayoutGetWidth(node),
.height = YGNodeLayoutGetHeight(node),
};
}
#pragma mark - Private
static YGSize YGMeasureView(
YGNodeRef node,
float width,
YGMeasureMode widthMode,
float height,
YGMeasureMode heightMode) {
const CGFloat constrainedWidth =
(widthMode == YGMeasureModeUndefined) ? CGFLOAT_MAX : width;
const CGFloat constrainedHeight =
(heightMode == YGMeasureModeUndefined) ? CGFLOAT_MAX : height;
UIView* view = (__bridge UIView*)YGNodeGetContext(node);
CGSize sizeThatFits = CGSizeZero;
// The default implementation of sizeThatFits: returns the existing size of
// the view. That means that if we want to layout an empty UIView, which
// already has got a frame set, its measured size should be CGSizeZero, but
// UIKit returns the existing size.
//
// See https://github.com/facebook/yoga/issues/606 for more information.
if (!view.yoga.isUIView || [view.subviews count] > 0) {
sizeThatFits = [view sizeThatFits:(CGSize){
.width = constrainedWidth,
.height = constrainedHeight,
}];
}
return (YGSize){
.width = YGSanitizeMeasurement(
constrainedWidth, sizeThatFits.width, widthMode),
.height = YGSanitizeMeasurement(
constrainedHeight, sizeThatFits.height, heightMode),
};
}
static CGFloat YGSanitizeMeasurement(
CGFloat constrainedSize,
CGFloat measuredSize,
YGMeasureMode measureMode) {
CGFloat result;
if (measureMode == YGMeasureModeExactly) {
result = constrainedSize;
} else if (measureMode == YGMeasureModeAtMost) {
result = MIN(constrainedSize, measuredSize);
} else {
result = measuredSize;
}
return result;
}
static BOOL YGNodeHasExactSameChildren(
const YGNodeRef node,
NSArray<UIView*>* subviews) {
if (YGNodeGetChildCount(node) != subviews.count) {
return NO;
}
for (int i = 0; i < subviews.count; i++) {
if (YGNodeGetChild(node, i) != subviews[i].yoga.node) {
return NO;
}
}
return YES;
}
static void YGAttachNodesFromViewHierachy(UIView* const view) {
YGLayout* const yoga = view.yoga;
const YGNodeRef node = yoga.node;
// Only leaf nodes should have a measure function
if (yoga.isLeaf) {
YGRemoveAllChildren(node);
YGNodeSetMeasureFunc(node, YGMeasureView);
} else {
YGNodeSetMeasureFunc(node, NULL);
NSMutableArray<UIView*>* subviewsToInclude =
[[NSMutableArray alloc] initWithCapacity:view.subviews.count];
for (UIView* subview in view.subviews) {
if (subview.yoga.isEnabled && subview.yoga.isIncludedInLayout) {
[subviewsToInclude addObject:subview];
}
}
if (!YGNodeHasExactSameChildren(node, subviewsToInclude)) {
YGRemoveAllChildren(node);
for (int i = 0; i < subviewsToInclude.count; i++) {
YGNodeInsertChild(node, subviewsToInclude[i].yoga.node, i);
}
}
for (UIView* const subview in subviewsToInclude) {
YGAttachNodesFromViewHierachy(subview);
}
}
}
static void YGRemoveAllChildren(const YGNodeRef node) {
if (node == NULL) {
return;
}
YGNodeRemoveAllChildren(node);
}
static CGFloat YGRoundPixelValue(CGFloat value) {
static CGFloat scale;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^() {
scale = [UIScreen mainScreen].scale;
});
return roundf(value * scale) / scale;
}
static void YGApplyLayoutToViewHierarchy(UIView* view, BOOL preserveOrigin) {
NSCAssert(
[NSThread isMainThread],
@"Framesetting should only be done on the main thread.");
const YGLayout* yoga = view.yoga;
if (!yoga.isIncludedInLayout) {
return;
}
YGNodeRef node = yoga.node;
const CGPoint topLeft = {
YGNodeLayoutGetLeft(node),
YGNodeLayoutGetTop(node),
};
const CGPoint bottomRight = {
topLeft.x + YGNodeLayoutGetWidth(node),
topLeft.y + YGNodeLayoutGetHeight(node),
};
const CGPoint origin = preserveOrigin ? view.frame.origin : CGPointZero;
view.frame = (CGRect){
.origin =
{
.x = YGRoundPixelValue(topLeft.x + origin.x),
.y = YGRoundPixelValue(topLeft.y + origin.y),
},
.size =
{
.width = YGRoundPixelValue(bottomRight.x) -
YGRoundPixelValue(topLeft.x),
.height = YGRoundPixelValue(bottomRight.y) -
YGRoundPixelValue(topLeft.y),
},
};
if (!yoga.isLeaf) {
for (NSUInteger i = 0; i < view.subviews.count; i++) {
YGApplyLayoutToViewHierarchy(view.subviews[i], NO);
}
}
}
@end

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import yoga;
postfix operator %
extension Int {
public static postfix func %(value: Int) -> YGValue {
return YGValue(value: Float(value), unit: .percent)
}
}
extension Float {
public static postfix func %(value: Float) -> YGValue {
return YGValue(value: value, unit: .percent)
}
}
extension CGFloat {
public static postfix func %(value: CGFloat) -> YGValue {
return YGValue(value: Float(value), unit: .percent)
}
}
extension YGValue : ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral {
public init(integerLiteral value: Int) {
self = YGValue(value: Float(value), unit: .point)
}
public init(floatLiteral value: Float) {
self = YGValue(value: value, unit: .point)
}
public init(_ value: Float) {
self = YGValue(value: value, unit: .point)
}
public init(_ value: CGFloat) {
self = YGValue(value: Float(value), unit: .point)
}
}

22
YogaKit/Tests/Info.plist Normal file
View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>com.facebook.${PRODUCT_NAME:rfc1034identifier}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View File

@@ -0,0 +1,753 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <XCTest/XCTest.h>
#import <YogaKit/UIView+Yoga.h>
#import <YogaKit/YGLayout+Private.h>
#import <yoga/Yoga.h>
@interface YogaKitTests : XCTestCase
@end
@implementation YogaKitTests
- (void)testConfigureLayoutIsNoOpWithNilBlock {
UIView* view = [[UIView alloc] initWithFrame:CGRectZero];
id block = nil;
XCTAssertNoThrow([view configureLayoutWithBlock:block]);
}
- (void)testConfigureLayoutBlockWorksWithValidBlock {
UIView* view = [[UIView alloc] initWithFrame:CGRectZero];
[view configureLayoutWithBlock:^(YGLayout* layout) {
XCTAssertNotNil(layout);
layout.isEnabled = YES;
layout.width = YGPointValue(25);
}];
XCTAssertTrue(view.yoga.isEnabled);
XCTAssertEqual(view.yoga.width.value, 25);
}
- (void)testNodesAreDeallocedWithSingleView {
__weak YGLayout* layoutRef = nil;
@autoreleasepool {
UIView* view = [[UIView alloc] initWithFrame:CGRectZero];
view.yoga.flexBasis = YGPointValue(1);
layoutRef = view.yoga;
XCTAssertNotNil(layoutRef);
view = nil;
}
XCTAssertNil(layoutRef);
}
- (void)testNodesAreDeallocedCascade {
__weak YGLayout* topLayout = nil;
__weak YGLayout* subviewLayout = nil;
@autoreleasepool {
UIView* view = [[UIView alloc] initWithFrame:CGRectZero];
topLayout = view.yoga;
topLayout.flexBasis = YGPointValue(1);
UIView* subview = [[UIView alloc] initWithFrame:CGRectZero];
subviewLayout = subview.yoga;
subviewLayout.flexBasis = YGPointValue(1);
view = nil;
}
XCTAssertNil(topLayout);
XCTAssertNil(subviewLayout);
}
- (void)testIsEnabled {
UIView* view = [[UIView alloc] initWithFrame:CGRectZero];
XCTAssertFalse(view.yoga.isEnabled);
view.yoga.isEnabled = YES;
XCTAssertTrue(view.yoga.isEnabled);
view.yoga.isEnabled = NO;
XCTAssertFalse(view.yoga.isEnabled);
}
- (void)testSizeThatFitsAsserts {
UIView* view = [[UIView alloc] initWithFrame:CGRectZero];
dispatch_sync(
dispatch_queue_create("com.facebook.Yoga.testing", DISPATCH_QUEUE_SERIAL),
^(void) {
XCTAssertThrows(view.yoga.intrinsicSize);
});
}
- (void)testSizeThatFitsSmoke {
UIView* container = [[UIView alloc] initWithFrame:CGRectZero];
container.yoga.isEnabled = YES;
container.yoga.flexDirection = YGFlexDirectionRow;
container.yoga.alignItems = YGAlignFlexStart;
UILabel* longTextLabel = [[UILabel alloc] initWithFrame:CGRectZero];
longTextLabel.text =
@"This is a very very very very very very very very long piece of text.";
longTextLabel.lineBreakMode = NSLineBreakByTruncatingTail;
longTextLabel.numberOfLines = 1;
longTextLabel.yoga.isEnabled = YES;
longTextLabel.yoga.flexShrink = 1;
[container addSubview:longTextLabel];
UIView* textBadgeView = [[UIView alloc] initWithFrame:CGRectZero];
textBadgeView.yoga.isEnabled = YES;
textBadgeView.yoga.margin = YGPointValue(0);
textBadgeView.yoga.width = YGPointValue(10);
textBadgeView.yoga.height = YGPointValue(10);
[container addSubview:textBadgeView];
const CGSize textBadgeViewSize = textBadgeView.yoga.intrinsicSize;
XCTAssertEqual(textBadgeViewSize.height, 10);
XCTAssertEqual(textBadgeViewSize.width, 10);
const CGSize containerSize = container.yoga.intrinsicSize;
const CGSize longTextLabelSize = longTextLabel.yoga.intrinsicSize;
XCTAssertEqual(longTextLabelSize.height, containerSize.height);
XCTAssertEqual(
longTextLabelSize.width + textBadgeView.yoga.intrinsicSize.width,
containerSize.width);
}
- (void)testSizeThatFitsEmptyView {
UIView* view = [[UIView alloc] initWithFrame:CGRectMake(10, 10, 200, 200)];
view.yoga.isEnabled = YES;
const CGSize viewSize = view.yoga.intrinsicSize;
XCTAssertEqual(viewSize.height, 0);
XCTAssertEqual(viewSize.width, 0);
}
- (void)testPreservingOrigin {
UIView* container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 75)];
container.yoga.isEnabled = YES;
UIView* view = [[UIView alloc] initWithFrame:CGRectZero];
view.yoga.isEnabled = YES;
view.yoga.flexBasis = YGPointValue(0);
view.yoga.flexGrow = 1;
[container addSubview:view];
UIView* view2 = [[UIView alloc] initWithFrame:CGRectZero];
view2.yoga.isEnabled = YES;
view2.yoga.marginTop = YGPointValue(25);
view2.yoga.flexBasis = YGPointValue(0);
view2.yoga.flexGrow = 1;
[container addSubview:view2];
[container.yoga applyLayoutPreservingOrigin:YES];
XCTAssertEqual(50, view2.frame.origin.y);
[view2.yoga applyLayoutPreservingOrigin:NO];
XCTAssertEqual(25, view2.frame.origin.y);
}
- (void)testContainerWithFlexibleWidthGetsCorrectlySized {
UIView* container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
container.yoga.isEnabled = YES;
UIView* view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
view.yoga.isEnabled = YES;
view.yoga.width = YGPointValue(100);
view.yoga.height = YGPointValue(100);
[container addSubview:view];
[container.yoga
applyLayoutPreservingOrigin:YES
dimensionFlexibility:YGDimensionFlexibilityFlexibleWidth];
XCTAssertEqual(100, container.frame.size.width);
XCTAssertEqual(200, container.frame.size.height);
}
- (void)testContainerWithFlexibleHeightGetsCorrectlySized {
UIView* container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
container.yoga.isEnabled = YES;
UIView* view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
view.yoga.isEnabled = YES;
view.yoga.width = YGPointValue(100);
view.yoga.height = YGPointValue(100);
[container addSubview:view];
[container.yoga
applyLayoutPreservingOrigin:YES
dimensionFlexibility:YGDimensionFlexibilityFlexibleHeight];
XCTAssertEqual(200, container.frame.size.width);
XCTAssertEqual(100, container.frame.size.height);
}
- (void)testContainerWithFlexibleWidthAndHeightGetsCorrectlySized {
UIView* container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
container.yoga.isEnabled = YES;
UIView* view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
view.yoga.isEnabled = YES;
view.yoga.width = YGPointValue(100);
view.yoga.height = YGPointValue(100);
[container addSubview:view];
[container.yoga
applyLayoutPreservingOrigin:YES
dimensionFlexibility:YGDimensionFlexibilityFlexibleWidth |
YGDimensionFlexibilityFlexibleHeight];
XCTAssertEqual(100, container.frame.size.width);
XCTAssertEqual(100, container.frame.size.height);
}
- (void)testMarkingDirtyOnlyWorksOnLeafNodes {
UIView* container = [[UIView alloc] initWithFrame:CGRectZero];
container.yoga.isEnabled = YES;
UIView* subview = [[UIView alloc] initWithFrame:CGRectZero];
subview.yoga.isEnabled = YES;
[container addSubview:subview];
XCTAssertFalse(container.yoga.isDirty);
[container.yoga markDirty];
XCTAssertFalse(container.yoga.isDirty);
XCTAssertFalse(subview.yoga.isDirty);
[subview.yoga markDirty];
XCTAssertTrue(subview.yoga.isDirty);
}
- (void)testThatMarkingLeafsAsDirtyWillTriggerASizeRecalculation {
UIView* container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 50)];
container.yoga.isEnabled = YES;
container.yoga.flexDirection = YGFlexDirectionRow;
container.yoga.alignItems = YGAlignFlexStart;
UILabel* view = [[UILabel alloc] initWithFrame:CGRectZero];
view.text = @"This is a short text.";
view.numberOfLines = 1;
view.yoga.isEnabled = YES;
[container addSubview:view];
[container.yoga applyLayoutPreservingOrigin:YES];
CGSize const viewSizeAfterFirstPass = view.frame.size;
view.text = @"This is a slightly longer text.";
XCTAssertTrue(CGSizeEqualToSize(view.frame.size, viewSizeAfterFirstPass));
[view.yoga markDirty];
[container.yoga applyLayoutPreservingOrigin:YES];
XCTAssertFalse(CGSizeEqualToSize(view.frame.size, viewSizeAfterFirstPass));
}
- (void)testFrameAndOriginPlacement {
// https://www.internalfb.com/intern/test/562950017690956
XCTSkip(
@"TODO: this test fails with actual dimensions off by 1px from expected");
const CGSize containerSize = CGSizeMake(320, 50);
UIView* container = [[UIView alloc]
initWithFrame:CGRectMake(
0, 0, containerSize.width, containerSize.height)];
container.yoga.isEnabled = YES;
container.yoga.flexDirection = YGFlexDirectionRow;
UIView* subview1 = [[UIView alloc] initWithFrame:CGRectZero];
subview1.yoga.isEnabled = YES;
subview1.yoga.flexGrow = 1;
[container addSubview:subview1];
UIView* subview2 = [[UIView alloc] initWithFrame:CGRectZero];
subview2.yoga.isEnabled = YES;
subview2.yoga.flexGrow = 1;
[container addSubview:subview2];
UIView* subview3 = [[UIView alloc] initWithFrame:CGRectZero];
subview3.yoga.isEnabled = YES;
subview3.yoga.flexGrow = 1;
[container addSubview:subview3];
[container.yoga applyLayoutPreservingOrigin:YES];
XCTAssertEqualWithAccuracy(
subview2.frame.origin.x, CGRectGetMaxX(subview1.frame), FLT_EPSILON);
XCTAssertEqualWithAccuracy(
subview3.frame.origin.x, CGRectGetMaxX(subview2.frame), FLT_EPSILON);
CGFloat totalWidth = 0;
for (UIView* view in container.subviews) {
totalWidth += view.bounds.size.width;
}
XCTAssertEqual(
containerSize.width,
totalWidth,
@"The container's width is %.6f, the subviews take up %.6f",
containerSize.width,
totalWidth);
}
- (void)testThatLayoutIsCorrectWhenWeSwapViewOrder {
const CGSize containerSize = CGSizeMake(300, 50);
UIView* container = [[UIView alloc]
initWithFrame:CGRectMake(
0, 0, containerSize.width, containerSize.height)];
container.yoga.isEnabled = YES;
container.yoga.flexDirection = YGFlexDirectionRow;
UIView* subview1 = [[UIView alloc] initWithFrame:CGRectZero];
subview1.yoga.isEnabled = YES;
subview1.yoga.flexGrow = 1;
[container addSubview:subview1];
UIView* subview2 = [[UIView alloc] initWithFrame:CGRectZero];
subview2.yoga.isEnabled = YES;
subview2.yoga.flexGrow = 1;
[container addSubview:subview2];
UIView* subview3 = [[UIView alloc] initWithFrame:CGRectZero];
subview3.yoga.isEnabled = YES;
subview3.yoga.flexGrow = 1;
[container addSubview:subview3];
[container.yoga applyLayoutPreservingOrigin:YES];
XCTAssertTrue(CGRectEqualToRect(subview1.frame, CGRectMake(0, 0, 100, 50)));
XCTAssertTrue(CGRectEqualToRect(subview2.frame, CGRectMake(100, 0, 100, 50)));
XCTAssertTrue(CGRectEqualToRect(subview3.frame, CGRectMake(200, 0, 100, 50)));
[container exchangeSubviewAtIndex:2 withSubviewAtIndex:0];
subview2.yoga.isIncludedInLayout = NO;
[container.yoga applyLayoutPreservingOrigin:YES];
XCTAssertTrue(CGRectEqualToRect(subview3.frame, CGRectMake(0, 0, 150, 50)));
XCTAssertTrue(CGRectEqualToRect(subview1.frame, CGRectMake(150, 0, 150, 50)));
// this frame shouldn't have been modified since last time.
XCTAssertTrue(CGRectEqualToRect(subview2.frame, CGRectMake(100, 0, 100, 50)));
}
- (void)testThatWeRespectIncludeInLayoutFlag {
const CGSize containerSize = CGSizeMake(300, 50);
UIView* container = [[UIView alloc]
initWithFrame:CGRectMake(
0, 0, containerSize.width, containerSize.height)];
container.yoga.isEnabled = YES;
container.yoga.flexDirection = YGFlexDirectionRow;
UIView* subview1 = [[UIView alloc] initWithFrame:CGRectZero];
subview1.yoga.isEnabled = YES;
subview1.yoga.flexGrow = 1;
[container addSubview:subview1];
UIView* subview2 = [[UIView alloc] initWithFrame:CGRectZero];
subview2.yoga.isEnabled = YES;
subview2.yoga.flexGrow = 1;
[container addSubview:subview2];
UIView* subview3 = [[UIView alloc] initWithFrame:CGRectZero];
subview3.yoga.isEnabled = YES;
subview3.yoga.flexGrow = 1;
[container addSubview:subview3];
[container.yoga applyLayoutPreservingOrigin:YES];
for (UIView* subview in container.subviews) {
XCTAssertEqual(subview.bounds.size.width, 100);
}
subview3.yoga.isIncludedInLayout = NO;
[container.yoga applyLayoutPreservingOrigin:YES];
XCTAssertEqual(subview1.bounds.size.width, 150);
XCTAssertEqual(subview2.bounds.size.width, 150);
// We don't set the frame to zero, so, it should be set to what it was
// previously at.
XCTAssertEqual(subview3.bounds.size.width, 100);
}
- (void)testThatNumberOfChildrenIsCorrectWhenWeIgnoreSubviews {
UIView* container = [[UIView alloc] initWithFrame:CGRectZero];
container.yoga.isEnabled = YES;
container.yoga.flexDirection = YGFlexDirectionRow;
UIView* subview1 = [[UIView alloc] initWithFrame:CGRectZero];
subview1.yoga.isEnabled = YES;
subview1.yoga.isIncludedInLayout = NO;
[container addSubview:subview1];
UIView* subview2 = [[UIView alloc] initWithFrame:CGRectZero];
subview2.yoga.isEnabled = YES;
subview2.yoga.isIncludedInLayout = NO;
[container addSubview:subview2];
UIView* subview3 = [[UIView alloc] initWithFrame:CGRectZero];
subview3.yoga.isEnabled = YES;
subview3.yoga.isIncludedInLayout = YES;
[container addSubview:subview3];
[container.yoga applyLayoutPreservingOrigin:YES];
XCTAssertEqual(container.yoga.numberOfChildren, 1);
subview2.yoga.isIncludedInLayout = YES;
[container.yoga applyLayoutPreservingOrigin:YES];
XCTAssertEqual(container.yoga.numberOfChildren, 2);
}
- (void)testThatViewNotIncludedInFirstLayoutPassAreIncludedInSecond {
UIView* container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 300, 50)];
container.yoga.isEnabled = YES;
container.yoga.flexDirection = YGFlexDirectionRow;
UIView* subview1 = [[UIView alloc] initWithFrame:CGRectZero];
subview1.yoga.isEnabled = YES;
subview1.yoga.flexGrow = 1;
[container addSubview:subview1];
UIView* subview2 = [[UIView alloc] initWithFrame:CGRectZero];
subview2.yoga.isEnabled = YES;
subview2.yoga.flexGrow = 1;
[container addSubview:subview2];
UIView* subview3 = [[UIView alloc] initWithFrame:CGRectZero];
subview3.yoga.isEnabled = YES;
subview3.yoga.flexGrow = 1;
subview3.yoga.isIncludedInLayout = NO;
[container addSubview:subview3];
[container.yoga applyLayoutPreservingOrigin:YES];
XCTAssertEqual(subview1.bounds.size.width, 150);
XCTAssertEqual(subview2.bounds.size.width, 150);
XCTAssertEqual(subview3.bounds.size.width, 0);
subview3.yoga.isIncludedInLayout = YES;
[container.yoga applyLayoutPreservingOrigin:YES];
XCTAssertEqual(subview1.bounds.size.width, 100);
XCTAssertEqual(subview2.bounds.size.width, 100);
XCTAssertEqual(subview3.bounds.size.width, 100);
}
- (void)testIsLeafFlag {
UIView* view = [[UIView alloc] initWithFrame:CGRectZero];
XCTAssertTrue(view.yoga.isLeaf);
for (int i = 0; i < 10; i++) {
UIView* subview = [[UIView alloc] initWithFrame:CGRectZero];
[view addSubview:subview];
}
XCTAssertTrue(view.yoga.isLeaf);
view.yoga.isEnabled = YES;
view.yoga.width = YGPointValue(50);
XCTAssertTrue(view.yoga.isLeaf);
UIView* const subview = view.subviews[0];
subview.yoga.isEnabled = YES;
subview.yoga.width = YGPointValue(50);
XCTAssertFalse(view.yoga.isLeaf);
}
- (void)testThatWeCorrectlyAttachNestedViews {
UIView* container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 300, 50)];
container.yoga.isEnabled = YES;
container.yoga.flexDirection = YGFlexDirectionColumn;
UIView* subview1 = [[UIView alloc] initWithFrame:CGRectZero];
subview1.yoga.isEnabled = YES;
subview1.yoga.width = YGPointValue(100);
subview1.yoga.flexGrow = 1;
subview1.yoga.flexDirection = YGFlexDirectionColumn;
[container addSubview:subview1];
UIView* subview2 = [[UIView alloc] initWithFrame:CGRectZero];
subview2.yoga.isEnabled = YES;
subview2.yoga.width = YGPointValue(150);
subview2.yoga.flexGrow = 1;
subview2.yoga.flexDirection = YGFlexDirectionColumn;
[container addSubview:subview2];
for (UIView* view in @[ subview1, subview2 ]) {
UIView* someView = [[UIView alloc] initWithFrame:CGRectZero];
someView.yoga.isEnabled = YES;
someView.yoga.flexGrow = 1;
[view addSubview:someView];
}
[container.yoga applyLayoutPreservingOrigin:YES];
// Add the same amount of new views, reapply layout.
for (UIView* view in @[ subview1, subview2 ]) {
UIView* someView = [[UIView alloc] initWithFrame:CGRectZero];
someView.yoga.isEnabled = YES;
someView.yoga.flexGrow = 1;
[view addSubview:someView];
}
[container.yoga applyLayoutPreservingOrigin:YES];
XCTAssertEqual(subview1.bounds.size.width, 100);
XCTAssertEqual(subview1.bounds.size.height, 25);
for (UIView* subview in subview1.subviews) {
const CGSize subviewSize = subview.bounds.size;
XCTAssertNotEqual(subviewSize.width, 0);
XCTAssertNotEqual(subviewSize.height, 0);
XCTAssertFalse(isnan(subviewSize.height));
XCTAssertFalse(isnan(subviewSize.width));
}
XCTAssertEqual(subview2.bounds.size.width, 150);
XCTAssertEqual(subview2.bounds.size.height, 25);
for (UIView* subview in subview2.subviews) {
const CGSize subviewSize = subview.bounds.size;
XCTAssertNotEqual(subviewSize.width, 0);
XCTAssertNotEqual(subviewSize.height, 0);
XCTAssertFalse(isnan(subviewSize.height));
XCTAssertFalse(isnan(subviewSize.width));
}
}
- (void)testThatANonLeafNodeCanBecomeALeafNode {
UIView* container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 300, 50)];
container.yoga.isEnabled = YES;
UIView* subview1 = [[UIView alloc] initWithFrame:CGRectZero];
subview1.yoga.isEnabled = YES;
[container addSubview:subview1];
UIView* subview2 = [[UIView alloc] initWithFrame:CGRectZero];
subview2.yoga.isEnabled = YES;
[subview1 addSubview:subview2];
[container.yoga applyLayoutPreservingOrigin:YES];
[subview2 removeFromSuperview];
[container.yoga applyLayoutPreservingOrigin:YES];
}
- (void)testPointPercent {
XCTAssertEqual(YGPointValue(1).value, 1);
XCTAssertEqual(YGPointValue(1).unit, YGUnitPoint);
XCTAssertEqual(YGPercentValue(2).value, 2);
XCTAssertEqual(YGPercentValue(2).unit, YGUnitPercent);
}
- (void)testPositionalPropertiesWork {
UIView* view = [[UIView alloc] initWithFrame:CGRectZero];
view.yoga.left = YGPointValue(1);
XCTAssertEqual(view.yoga.left.value, 1);
XCTAssertEqual(view.yoga.left.unit, YGUnitPoint);
view.yoga.left = YGPercentValue(2);
XCTAssertEqual(view.yoga.left.value, 2);
XCTAssertEqual(view.yoga.left.unit, YGUnitPercent);
view.yoga.right = YGPointValue(3);
XCTAssertEqual(view.yoga.right.value, 3);
XCTAssertEqual(view.yoga.right.unit, YGUnitPoint);
view.yoga.right = YGPercentValue(4);
XCTAssertEqual(view.yoga.right.value, 4);
XCTAssertEqual(view.yoga.right.unit, YGUnitPercent);
view.yoga.top = YGPointValue(5);
XCTAssertEqual(view.yoga.top.value, 5);
XCTAssertEqual(view.yoga.top.unit, YGUnitPoint);
view.yoga.top = YGPercentValue(6);
XCTAssertEqual(view.yoga.top.value, 6);
XCTAssertEqual(view.yoga.top.unit, YGUnitPercent);
view.yoga.bottom = YGPointValue(7);
XCTAssertEqual(view.yoga.bottom.value, 7);
XCTAssertEqual(view.yoga.bottom.unit, YGUnitPoint);
view.yoga.bottom = YGPercentValue(8);
XCTAssertEqual(view.yoga.bottom.value, 8);
XCTAssertEqual(view.yoga.bottom.unit, YGUnitPercent);
view.yoga.start = YGPointValue(9);
XCTAssertEqual(view.yoga.start.value, 9);
XCTAssertEqual(view.yoga.start.unit, YGUnitPoint);
view.yoga.start = YGPercentValue(10);
XCTAssertEqual(view.yoga.start.value, 10);
XCTAssertEqual(view.yoga.start.unit, YGUnitPercent);
view.yoga.end = YGPointValue(11);
XCTAssertEqual(view.yoga.end.value, 11);
XCTAssertEqual(view.yoga.end.unit, YGUnitPoint);
view.yoga.end = YGPercentValue(12);
XCTAssertEqual(view.yoga.end.value, 12);
XCTAssertEqual(view.yoga.end.unit, YGUnitPercent);
}
- (void)testMarginPropertiesWork {
UIView* view = [[UIView alloc] initWithFrame:CGRectZero];
view.yoga.margin = YGPointValue(1);
XCTAssertEqual(view.yoga.margin.value, 1);
XCTAssertEqual(view.yoga.margin.unit, YGUnitPoint);
view.yoga.margin = YGPercentValue(2);
XCTAssertEqual(view.yoga.margin.value, 2);
XCTAssertEqual(view.yoga.margin.unit, YGUnitPercent);
view.yoga.marginHorizontal = YGPointValue(3);
XCTAssertEqual(view.yoga.marginHorizontal.value, 3);
XCTAssertEqual(view.yoga.marginHorizontal.unit, YGUnitPoint);
view.yoga.marginHorizontal = YGPercentValue(4);
XCTAssertEqual(view.yoga.marginHorizontal.value, 4);
XCTAssertEqual(view.yoga.marginHorizontal.unit, YGUnitPercent);
view.yoga.marginVertical = YGPointValue(5);
XCTAssertEqual(view.yoga.marginVertical.value, 5);
XCTAssertEqual(view.yoga.marginVertical.unit, YGUnitPoint);
view.yoga.marginVertical = YGPercentValue(6);
XCTAssertEqual(view.yoga.marginVertical.value, 6);
XCTAssertEqual(view.yoga.marginVertical.unit, YGUnitPercent);
view.yoga.marginLeft = YGPointValue(7);
XCTAssertEqual(view.yoga.marginLeft.value, 7);
XCTAssertEqual(view.yoga.marginLeft.unit, YGUnitPoint);
view.yoga.marginLeft = YGPercentValue(8);
XCTAssertEqual(view.yoga.marginLeft.value, 8);
XCTAssertEqual(view.yoga.marginLeft.unit, YGUnitPercent);
view.yoga.marginRight = YGPointValue(9);
XCTAssertEqual(view.yoga.marginRight.value, 9);
XCTAssertEqual(view.yoga.marginRight.unit, YGUnitPoint);
view.yoga.marginRight = YGPercentValue(10);
XCTAssertEqual(view.yoga.marginRight.value, 10);
XCTAssertEqual(view.yoga.marginRight.unit, YGUnitPercent);
view.yoga.marginTop = YGPointValue(11);
XCTAssertEqual(view.yoga.marginTop.value, 11);
XCTAssertEqual(view.yoga.marginTop.unit, YGUnitPoint);
view.yoga.marginTop = YGPercentValue(12);
XCTAssertEqual(view.yoga.marginTop.value, 12);
XCTAssertEqual(view.yoga.marginTop.unit, YGUnitPercent);
view.yoga.marginBottom = YGPointValue(13);
XCTAssertEqual(view.yoga.marginBottom.value, 13);
XCTAssertEqual(view.yoga.marginBottom.unit, YGUnitPoint);
view.yoga.marginBottom = YGPercentValue(14);
XCTAssertEqual(view.yoga.marginBottom.value, 14);
XCTAssertEqual(view.yoga.marginBottom.unit, YGUnitPercent);
view.yoga.marginStart = YGPointValue(15);
XCTAssertEqual(view.yoga.marginStart.value, 15);
XCTAssertEqual(view.yoga.marginStart.unit, YGUnitPoint);
view.yoga.marginStart = YGPercentValue(16);
XCTAssertEqual(view.yoga.marginStart.value, 16);
XCTAssertEqual(view.yoga.marginStart.unit, YGUnitPercent);
view.yoga.marginEnd = YGPointValue(17);
XCTAssertEqual(view.yoga.marginEnd.value, 17);
XCTAssertEqual(view.yoga.marginEnd.unit, YGUnitPoint);
view.yoga.marginEnd = YGPercentValue(18);
XCTAssertEqual(view.yoga.marginEnd.value, 18);
XCTAssertEqual(view.yoga.marginEnd.unit, YGUnitPercent);
}
- (void)testPaddingPropertiesWork {
UIView* view = [[UIView alloc] initWithFrame:CGRectZero];
view.yoga.padding = YGPointValue(1);
XCTAssertEqual(view.yoga.padding.value, 1);
XCTAssertEqual(view.yoga.padding.unit, YGUnitPoint);
view.yoga.padding = YGPercentValue(2);
XCTAssertEqual(view.yoga.padding.value, 2);
XCTAssertEqual(view.yoga.padding.unit, YGUnitPercent);
view.yoga.paddingHorizontal = YGPointValue(3);
XCTAssertEqual(view.yoga.paddingHorizontal.value, 3);
XCTAssertEqual(view.yoga.paddingHorizontal.unit, YGUnitPoint);
view.yoga.paddingHorizontal = YGPercentValue(4);
XCTAssertEqual(view.yoga.paddingHorizontal.value, 4);
XCTAssertEqual(view.yoga.paddingHorizontal.unit, YGUnitPercent);
view.yoga.paddingVertical = YGPointValue(5);
XCTAssertEqual(view.yoga.paddingVertical.value, 5);
XCTAssertEqual(view.yoga.paddingVertical.unit, YGUnitPoint);
view.yoga.paddingVertical = YGPercentValue(6);
XCTAssertEqual(view.yoga.paddingVertical.value, 6);
XCTAssertEqual(view.yoga.paddingVertical.unit, YGUnitPercent);
view.yoga.paddingLeft = YGPointValue(7);
XCTAssertEqual(view.yoga.paddingLeft.value, 7);
XCTAssertEqual(view.yoga.paddingLeft.unit, YGUnitPoint);
view.yoga.paddingLeft = YGPercentValue(8);
XCTAssertEqual(view.yoga.paddingLeft.value, 8);
XCTAssertEqual(view.yoga.paddingLeft.unit, YGUnitPercent);
view.yoga.paddingRight = YGPointValue(9);
XCTAssertEqual(view.yoga.paddingRight.value, 9);
XCTAssertEqual(view.yoga.paddingRight.unit, YGUnitPoint);
view.yoga.paddingRight = YGPercentValue(10);
XCTAssertEqual(view.yoga.paddingRight.value, 10);
XCTAssertEqual(view.yoga.paddingRight.unit, YGUnitPercent);
view.yoga.paddingTop = YGPointValue(11);
XCTAssertEqual(view.yoga.paddingTop.value, 11);
XCTAssertEqual(view.yoga.paddingTop.unit, YGUnitPoint);
view.yoga.paddingTop = YGPercentValue(12);
XCTAssertEqual(view.yoga.paddingTop.value, 12);
XCTAssertEqual(view.yoga.paddingTop.unit, YGUnitPercent);
view.yoga.paddingBottom = YGPointValue(13);
XCTAssertEqual(view.yoga.paddingBottom.value, 13);
XCTAssertEqual(view.yoga.paddingBottom.unit, YGUnitPoint);
view.yoga.paddingBottom = YGPercentValue(14);
XCTAssertEqual(view.yoga.paddingBottom.value, 14);
XCTAssertEqual(view.yoga.paddingBottom.unit, YGUnitPercent);
view.yoga.paddingStart = YGPointValue(15);
XCTAssertEqual(view.yoga.paddingStart.value, 15);
XCTAssertEqual(view.yoga.paddingStart.unit, YGUnitPoint);
view.yoga.paddingStart = YGPercentValue(16);
XCTAssertEqual(view.yoga.paddingStart.value, 16);
XCTAssertEqual(view.yoga.paddingStart.unit, YGUnitPercent);
view.yoga.paddingEnd = YGPointValue(17);
XCTAssertEqual(view.yoga.paddingEnd.value, 17);
XCTAssertEqual(view.yoga.paddingEnd.unit, YGUnitPoint);
view.yoga.paddingEnd = YGPercentValue(18);
XCTAssertEqual(view.yoga.paddingEnd.value, 18);
XCTAssertEqual(view.yoga.paddingEnd.unit, YGUnitPercent);
}
- (void)testBorderWidthPropertiesWork {
UIView* view = [[UIView alloc] initWithFrame:CGRectZero];
view.yoga.borderWidth = 1;
XCTAssertEqual(view.yoga.borderWidth, 1);
view.yoga.borderLeftWidth = 2;
XCTAssertEqual(view.yoga.borderLeftWidth, 2);
view.yoga.borderRightWidth = 3;
XCTAssertEqual(view.yoga.borderRightWidth, 3);
view.yoga.borderTopWidth = 4;
XCTAssertEqual(view.yoga.borderTopWidth, 4);
view.yoga.borderBottomWidth = 5;
XCTAssertEqual(view.yoga.borderBottomWidth, 5);
view.yoga.borderStartWidth = 6;
XCTAssertEqual(view.yoga.borderStartWidth, 6);
view.yoga.borderEndWidth = 7;
XCTAssertEqual(view.yoga.borderEndWidth, 7);
}
@end

View File

@@ -0,0 +1,17 @@
use_frameworks!
platform :ios, "13.4"
target 'YogaKitSample' do
pod 'YogaKit', :path => '../../YogaKit.podspec'
pod 'Yoga', :path => '../../Yoga.podspec'
pod 'IGListKit', '~> 4.0.0'
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.4'
end
end
end

View File

@@ -0,0 +1,33 @@
PODS:
- IGListDiffKit (4.0.0)
- IGListKit (4.0.0):
- IGListDiffKit (= 4.0.0)
- Yoga (2.0.0-beta.1)
- YogaKit (2.0.0-beta.1):
- Yoga (~> 2.0.0-beta.1)
DEPENDENCIES:
- IGListKit (~> 4.0.0)
- Yoga (from `../../Yoga.podspec`)
- YogaKit (from `../../YogaKit.podspec`)
SPEC REPOS:
trunk:
- IGListDiffKit
- IGListKit
EXTERNAL SOURCES:
Yoga:
:path: "../../Yoga.podspec"
YogaKit:
:path: "../../YogaKit.podspec"
SPEC CHECKSUMS:
IGListDiffKit: 665d6cf43ce726e676013db9c7d6c4294259b6b2
IGListKit: fd5a5d21935298f5849fa49d426843cff97b77c7
Yoga: 090c6851e548d085d6211b5466b57ba8f4013a90
YogaKit: 5ae1939cd7516fdf6c343abb4b4aa381c9911ccf
PODFILE CHECKSUM: e5d9841ef739884db00a29f2d529d16bf247a4ca
COCOAPODS: 1.12.1

View File

@@ -0,0 +1,518 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
13687D531DF8748400E7C260 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13687D521DF8748400E7C260 /* Assets.xcassets */; };
13687D851DF87D1E00E7C260 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 13687D841DF87D1E00E7C260 /* UIKit.framework */; };
13687D871DF87D2400E7C260 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 13687D861DF87D2400E7C260 /* Foundation.framework */; };
15A7CB5995C9DAB1C8803834 /* Pods_YogaKitSample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C80A931E90C7F3088CB86822 /* Pods_YogaKitSample.framework */; };
40BD9F461E477A09002790A9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40BD9F451E477A09002790A9 /* AppDelegate.swift */; };
40BD9F4B1E47850C002790A9 /* BasicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40BD9F4A1E47850C002790A9 /* BasicViewController.swift */; };
40BD9F501E479079002790A9 /* SingleLabelCollectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40BD9F4F1E479079002790A9 /* SingleLabelCollectionCell.swift */; };
40BD9F521E479173002790A9 /* LayoutInclusionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40BD9F511E479173002790A9 /* LayoutInclusionViewController.swift */; };
638A94481E1F06D100A726AD /* ExamplesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 638A94471E1F06D100A726AD /* ExamplesViewController.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
638A94541E215CC800A726AD /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 13687D3B1DF8748300E7C260 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 13687D421DF8748300E7C260;
remoteInfo = YogaKitSample;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
13687D771DF878A000E7C260 /* yoga */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = include/yoga;
dstSubfolderSpec = 16;
files = (
);
name = yoga;
runOnlyForDeploymentPostprocessing = 0;
};
13687D7B1DF878CE00E7C260 /* YogaKit */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = include/YogaKit;
dstSubfolderSpec = 16;
files = (
);
name = YogaKit;
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
13687D431DF8748400E7C260 /* YogaKitSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = YogaKitSample.app; sourceTree = BUILT_PRODUCTS_DIR; };
13687D521DF8748400E7C260 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
13687D571DF8748400E7C260 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
13687D841DF87D1E00E7C260 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
13687D861DF87D2400E7C260 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
1D2FF4D5FCA6A8C54A4074A3 /* Pods-YogaKitSample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-YogaKitSample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-YogaKitSample/Pods-YogaKitSample.debug.xcconfig"; sourceTree = "<group>"; };
40BD9F451E477A09002790A9 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
40BD9F4A1E47850C002790A9 /* BasicViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BasicViewController.swift; path = ViewControllers/BasicViewController.swift; sourceTree = "<group>"; };
40BD9F4F1E479079002790A9 /* SingleLabelCollectionCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SingleLabelCollectionCell.swift; path = Views/SingleLabelCollectionCell.swift; sourceTree = "<group>"; };
40BD9F511E479173002790A9 /* LayoutInclusionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LayoutInclusionViewController.swift; path = ViewControllers/LayoutInclusionViewController.swift; sourceTree = "<group>"; };
638A94471E1F06D100A726AD /* ExamplesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExamplesViewController.swift; sourceTree = "<group>"; };
638A944F1E215CC800A726AD /* YogaKitSampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = YogaKitSampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
82F0896A88112E957EF37C7F /* Pods-YogaKitSample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-YogaKitSample.release.xcconfig"; path = "Pods/Target Support Files/Pods-YogaKitSample/Pods-YogaKitSample.release.xcconfig"; sourceTree = "<group>"; };
C80A931E90C7F3088CB86822 /* Pods_YogaKitSample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_YogaKitSample.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
13687D401DF8748300E7C260 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
13687D871DF87D2400E7C260 /* Foundation.framework in Frameworks */,
13687D851DF87D1E00E7C260 /* UIKit.framework in Frameworks */,
15A7CB5995C9DAB1C8803834 /* Pods_YogaKitSample.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
638A944C1E215CC800A726AD /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
13687D3A1DF8748300E7C260 = {
isa = PBXGroup;
children = (
13687D451DF8748400E7C260 /* YogaKitSample */,
13687D441DF8748400E7C260 /* Products */,
13687D831DF87D1E00E7C260 /* Frameworks */,
E1C759E3C8E84821213ECE8D /* Pods */,
);
sourceTree = "<group>";
};
13687D441DF8748400E7C260 /* Products */ = {
isa = PBXGroup;
children = (
13687D431DF8748400E7C260 /* YogaKitSample.app */,
638A944F1E215CC800A726AD /* YogaKitSampleTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
13687D451DF8748400E7C260 /* YogaKitSample */ = {
isa = PBXGroup;
children = (
40BD9F4E1E47902F002790A9 /* Views */,
40BD9F481E4784B3002790A9 /* ViewControllers */,
638A94471E1F06D100A726AD /* ExamplesViewController.swift */,
13687D521DF8748400E7C260 /* Assets.xcassets */,
13687D571DF8748400E7C260 /* Info.plist */,
40BD9F451E477A09002790A9 /* AppDelegate.swift */,
);
path = YogaKitSample;
sourceTree = "<group>";
};
13687D831DF87D1E00E7C260 /* Frameworks */ = {
isa = PBXGroup;
children = (
13687D861DF87D2400E7C260 /* Foundation.framework */,
13687D841DF87D1E00E7C260 /* UIKit.framework */,
C80A931E90C7F3088CB86822 /* Pods_YogaKitSample.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
40BD9F481E4784B3002790A9 /* ViewControllers */ = {
isa = PBXGroup;
children = (
40BD9F4A1E47850C002790A9 /* BasicViewController.swift */,
40BD9F511E479173002790A9 /* LayoutInclusionViewController.swift */,
);
name = ViewControllers;
sourceTree = "<group>";
};
40BD9F4E1E47902F002790A9 /* Views */ = {
isa = PBXGroup;
children = (
40BD9F4F1E479079002790A9 /* SingleLabelCollectionCell.swift */,
);
name = Views;
sourceTree = "<group>";
};
E1C759E3C8E84821213ECE8D /* Pods */ = {
isa = PBXGroup;
children = (
1D2FF4D5FCA6A8C54A4074A3 /* Pods-YogaKitSample.debug.xcconfig */,
82F0896A88112E957EF37C7F /* Pods-YogaKitSample.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
13687D421DF8748300E7C260 /* YogaKitSample */ = {
isa = PBXNativeTarget;
buildConfigurationList = 13687D5A1DF8748400E7C260 /* Build configuration list for PBXNativeTarget "YogaKitSample" */;
buildPhases = (
513B543F92B2E4F4D1EE1CE7 /* [CP] Check Pods Manifest.lock */,
13687D771DF878A000E7C260 /* yoga */,
13687D7B1DF878CE00E7C260 /* YogaKit */,
13687D3F1DF8748300E7C260 /* Sources */,
13687D401DF8748300E7C260 /* Frameworks */,
13687D411DF8748300E7C260 /* Resources */,
FA2FB9DD6471BDD3FBCE503B /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = YogaKitSample;
productName = YogaKitSample;
productReference = 13687D431DF8748400E7C260 /* YogaKitSample.app */;
productType = "com.apple.product-type.application";
};
638A944E1E215CC800A726AD /* YogaKitSampleTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 638A94561E215CC800A726AD /* Build configuration list for PBXNativeTarget "YogaKitSampleTests" */;
buildPhases = (
638A944B1E215CC800A726AD /* Sources */,
638A944C1E215CC800A726AD /* Frameworks */,
638A944D1E215CC800A726AD /* Resources */,
);
buildRules = (
);
dependencies = (
638A94551E215CC800A726AD /* PBXTargetDependency */,
);
name = YogaKitSampleTests;
productName = YogaKitSampleTests;
productReference = 638A944F1E215CC800A726AD /* YogaKitSampleTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
13687D3B1DF8748300E7C260 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0820;
ORGANIZATIONNAME = facebook;
TargetAttributes = {
13687D421DF8748300E7C260 = {
CreatedOnToolsVersion = 8.1;
LastSwiftMigration = 0820;
ProvisioningStyle = Automatic;
};
638A944E1E215CC800A726AD = {
CreatedOnToolsVersion = 8.2.1;
ProvisioningStyle = Automatic;
TestTargetID = 13687D421DF8748300E7C260;
};
};
};
buildConfigurationList = 13687D3E1DF8748300E7C260 /* Build configuration list for PBXProject "YogaKitSample" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 13687D3A1DF8748300E7C260;
productRefGroup = 13687D441DF8748400E7C260 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
13687D421DF8748300E7C260 /* YogaKitSample */,
638A944E1E215CC800A726AD /* YogaKitSampleTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
13687D411DF8748300E7C260 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
13687D531DF8748400E7C260 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
638A944D1E215CC800A726AD /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
513B543F92B2E4F4D1EE1CE7 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-YogaKitSample-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
FA2FB9DD6471BDD3FBCE503B /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-YogaKitSample/Pods-YogaKitSample-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/IGListDiffKit/IGListDiffKit.framework",
"${BUILT_PRODUCTS_DIR}/IGListKit/IGListKit.framework",
"${BUILT_PRODUCTS_DIR}/Yoga/yoga.framework",
"${BUILT_PRODUCTS_DIR}/YogaKit/YogaKit.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IGListDiffKit.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IGListKit.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/yoga.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YogaKit.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-YogaKitSample/Pods-YogaKitSample-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
13687D3F1DF8748300E7C260 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
40BD9F501E479079002790A9 /* SingleLabelCollectionCell.swift in Sources */,
40BD9F521E479173002790A9 /* LayoutInclusionViewController.swift in Sources */,
638A94481E1F06D100A726AD /* ExamplesViewController.swift in Sources */,
40BD9F4B1E47850C002790A9 /* BasicViewController.swift in Sources */,
40BD9F461E477A09002790A9 /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
638A944B1E215CC800A726AD /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
638A94551E215CC800A726AD /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 13687D421DF8748300E7C260 /* YogaKitSample */;
targetProxy = 638A94541E215CC800A726AD /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
13687D581DF8748400E7C260 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_SUSPICIOUS_MOVES = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGNING_ALLOWED = NO;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
13687D591DF8748400E7C260 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_SUSPICIOUS_MOVES = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGNING_ALLOWED = NO;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
13687D5B1DF8748400E7C260 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 1D2FF4D5FCA6A8C54A4074A3 /* Pods-YogaKitSample.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
INFOPLIST_FILE = YogaKitSample/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.YogaKitSample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_INSTALL_OBJC_HEADER = NO;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.1;
};
name = Debug;
};
13687D5C1DF8748400E7C260 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 82F0896A88112E957EF37C7F /* Pods-YogaKitSample.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
INFOPLIST_FILE = YogaKitSample/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.YogaKitSample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_INSTALL_OBJC_HEADER = NO;
SWIFT_VERSION = 5.1;
};
name = Release;
};
638A94571E215CC800A726AD /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
INFOPLIST_FILE = YogaKitSampleTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.YogaKitSampleTests;
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/YogaKitSample.app/YogaKitSample";
};
name = Debug;
};
638A94581E215CC800A726AD /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
INFOPLIST_FILE = YogaKitSampleTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.YogaKitSampleTests;
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/YogaKitSample.app/YogaKitSample";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
13687D3E1DF8748300E7C260 /* Build configuration list for PBXProject "YogaKitSample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
13687D581DF8748400E7C260 /* Debug */,
13687D591DF8748400E7C260 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
13687D5A1DF8748400E7C260 /* Build configuration list for PBXNativeTarget "YogaKitSample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
13687D5B1DF8748400E7C260 /* Debug */,
13687D5C1DF8748400E7C260 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
638A94561E215CC800A726AD /* Build configuration list for PBXNativeTarget "YogaKitSampleTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
638A94571E215CC800A726AD /* Debug */,
638A94581E215CC800A726AD /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 13687D3B1DF8748300E7C260 /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:YogaKitSample.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0820"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13687D421DF8748300E7C260"
BuildableName = "YogaKitSample.app"
BlueprintName = "YogaKitSample"
ReferencedContainer = "container:YogaKitSample.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "638A944E1E215CC800A726AD"
BuildableName = "YogaKitSampleTests.xctest"
BlueprintName = "YogaKitSampleTests"
ReferencedContainer = "container:YogaKitSample.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13687D421DF8748300E7C260"
BuildableName = "YogaKitSample.app"
BlueprintName = "YogaKitSample"
ReferencedContainer = "container:YogaKitSample.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13687D421DF8748300E7C260"
BuildableName = "YogaKitSample.app"
BlueprintName = "YogaKitSample"
ReferencedContainer = "container:YogaKitSample.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13687D421DF8748300E7C260"
BuildableName = "YogaKitSample.app"
BlueprintName = "YogaKitSample"
ReferencedContainer = "container:YogaKitSample.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:YogaKitSample.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import Foundation
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func applicationDidFinishLaunching(_ application: UIApplication) {
self.window = UIWindow(frame: UIScreen.main.bounds)
if let window = self.window {
let navigationController = UINavigationController(rootViewController: ExamplesViewController())
navigationController.navigationBar.isTranslucent = false
window.rootViewController = navigationController
window.backgroundColor = .white
window.makeKeyAndVisible()
}
}
}

View File

@@ -0,0 +1,74 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Yoga-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Yoga-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Yoga-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Yoga-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Yoga-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Yoga-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Yoga-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "57x57",
"idiom" : "iphone",
"filename" : "Yoga-57x57@1x.png",
"scale" : "1x"
},
{
"size" : "57x57",
"idiom" : "iphone",
"filename" : "Yoga-57x57@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Yoga-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Yoga-60x60@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 868 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import UIKit
import IGListKit
private final class ExampleModel {
let title: String
let controllerClass: UIViewController.Type
init(title: String, controllerClass: UIViewController.Type) {
self.title = title
self.controllerClass = controllerClass
}
}
extension ExampleModel: ListDiffable {
fileprivate func diffIdentifier() -> NSObjectProtocol {
return title as NSString
}
fileprivate func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
guard let otherObj = object as? ExampleModel else { return false }
return (title == otherObj.title) &&
(controllerClass == otherObj.controllerClass)
}
}
final class ExamplesViewController: UIViewController, ListAdapterDataSource, ListSingleSectionControllerDelegate {
private lazy var adapter: ListAdapter = {
return ListAdapter(updater: ListAdapterUpdater(), viewController: self, workingRangeSize: 0)
}()
private lazy var collectionView: UICollectionView = {
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
collectionView.backgroundColor = UIColor.systemBackground
return collectionView
}()
// Update this to array to create more examples.
private let models: [ExampleModel] = [ExampleModel(title: "Basic Layout", controllerClass: BasicViewController.self),
ExampleModel(title: "Exclude Views in Layout", controllerClass: LayoutInclusionViewController.self)]
//MARK: UIViewController
override func viewDidLoad() {
super.viewDidLoad()
title = "Examples"
view.addSubview(collectionView)
adapter.collectionView = collectionView
adapter.dataSource = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.frame = view.bounds
}
//MARK: IGListAdapterDataSource
func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
return models as [ListDiffable]
}
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
let sizeBlock: ListSingleSectionCellSizeBlock = { (model, context) in
return CGSize(width: (context?.containerSize.width)!, height: 75.0)
}
let configureBlock: ListSingleSectionCellConfigureBlock = { (model, cell) in
guard let m = model as? ExampleModel, let c = cell as? SingleLabelCollectionCell else {
return
}
c.label.text = m.title
}
let sectionController = ListSingleSectionController(cellClass: SingleLabelCollectionCell.self,
configureBlock: configureBlock,
sizeBlock: sizeBlock)
sectionController.selectionDelegate = self
return sectionController
}
func emptyView(for listAdapter: ListAdapter) -> UIView? { return nil }
//MARK: IGListSingleSectionControllerDelegate
func didSelect(_ sectionController: ListSingleSectionController, with object: Any) {
let section = adapter.section(for: sectionController)
let model = models[section]
let controller = model.controllerClass.init()
controller.title = model.title
self.navigationController?.pushViewController(controller, animated: true)
}
}

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string></string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,70 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import UIKit
import IGListKit
import YogaKit
struct DemoItem {
let name: String
root.backgroundColor = .red
root.yoga.isEnabled = true
root.yoga.width = YGValue(self.view.bounds.size.width)
root.yoga.height = YGValue(self.view.bounds.size.height)
root.yoga.alignItems = .center
}
final class SwiftViewController: UIViewController, IGListAdapterDataSource {
lazy var adapter: IGListAdapter = {
return IGListAdapter(updater: IGListAdapterUpdater(), viewController: self, workingRangeSize: 0)
}()
let collectionView = IGListCollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
//MARK: UIViewController
override func viewDidLoad() {
super.viewDidLoad()
title = "YogaKit Examples"
view.addSubview(collectionView)
adapter.collectionView = collectionView
adapter.dataSource = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.frame = view.bounds
}
//MARK: IGListAdapterDataSource
func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] {
return ["Dustin" as IGListDiffable, "Ryan" as IGListDiffable]
}
func listAdapter(_ listAdapter: IGListAdapter, sectionControllerFor object: Any) -> IGListSectionController {
let sizeBlock: IGListSingleSectionCellSizeBlock = { (model, context) in
return CGSize(width: (context?.containerSize.width)!, height: 100.0)
}
let configureBlock: IGListSingleSectionCellConfigureBlock = { (model, cell) in
guard let m = model as? String else {
return
}
cell.backgroundColor = (m == "Dustin") ? .blue : .red
}
return IGListSingleSectionController(cellClass: UICollectionViewCell.self,
configureBlock: configureBlock,
sizeBlock: sizeBlock)
}
func emptyView(for listAdapter: IGListAdapter) -> UIView? {
return nil
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "ViewController.h"
#import <YogaKit/UIView+Yoga.h>
@implementation ViewController
- (void)viewDidLoad {
UIView* root = self.view;
root.backgroundColor = [UIColor redColor];
root.yoga.isEnabled = YES;
root.yoga.width = YGPointValue(self.view.bounds.size.width);
root.yoga.height = YGPointValue(self.view.bounds.size.height);
root.yoga.alignItems = YGAlignCenter;
root.yoga.justifyContent = YGJustifyCenter;
UIView* child1 = [UIView new];
child1.backgroundColor = [UIColor blueColor];
child1.yoga.isEnabled = YES;
child1.yoga.width = YGPointValue(100);
child1.yoga.height = YGPointValue(100);
UIView* child2 = [UIView new];
child2.backgroundColor = [UIColor greenColor];
child2.frame = (CGRect){
.size = {
.width = 200,
.height = 100,
}};
UIView* child3 = [UIView new];
child3.backgroundColor = [UIColor yellowColor];
child3.frame = (CGRect){
.size = {
.width = 100,
.height = 100,
}};
[child2 addSubview:child3];
[root addSubview:child1];
[root addSubview:child2];
[root.yoga applyLayoutPreservingOrigin:NO];
}
@end

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import UIKit
import YogaKit
final class BasicViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let containerSize = self.view.bounds.size
view.backgroundColor = UIColor.systemBackground
view.configureLayout { (layout) in
layout.isEnabled = true
layout.width = YGValue(containerSize.width)
layout.height = YGValue(containerSize.height)
layout.alignItems = .center
layout.justifyContent = .center
}
let child1 = UIView()
child1.backgroundColor = .blue
child1.configureLayout { (layout) in
layout.isEnabled = true
layout.width = 100
layout.height = 10
layout.marginBottom = 25
}
view.addSubview(child1)
let child2 = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
child2.backgroundColor = .green
child2.configureLayout { (layout) in
layout.isEnabled = true
layout.alignSelf = .flexEnd
layout.width = 200
layout.height = 200
}
view.addSubview(child2)
let child3 = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
child3.backgroundColor = .yellow
child3.configureLayout { (layout) in
layout.isEnabled = true
layout.alignSelf = .flexStart
layout.width = 100
layout.height = 100
}
view.addSubview(child3)
view.yoga.applyLayout(preservingOrigin: true)
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import UIKit
import YogaKit
final class LayoutInclusionViewController: UIViewController {
private let button: UIButton = UIButton(type: .system)
private let disappearingView: UIView = UIView(frame: .zero)
private let contentView: UIView = UIView(frame: .zero)
override func viewDidLoad() {
super.viewDidLoad()
let root = self.view!
root.backgroundColor = .white
root.configureLayout { (layout) in
layout.isEnabled = true
layout.flexDirection = .column
layout.justifyContent = .spaceAround
}
contentView.backgroundColor = .clear
contentView.layer.borderColor = UIColor.lightGray.cgColor
contentView.layer.borderWidth = 1.0
contentView.configureLayout { (layout) in
layout.isEnabled = true
layout.height = 300
layout.width = YGValue(self.view.bounds.size.width)
layout.flexDirection = .row
layout.justifyContent = .center
layout.paddingHorizontal = 25
}
self.view.addSubview(contentView)
let redView = UIView(frame: .zero)
redView.backgroundColor = .red
redView.configureLayout { (layout) in
layout.isEnabled = true
layout.flexGrow = 1
layout.flexShrink = 1
}
contentView.addSubview(redView)
disappearingView.backgroundColor = .blue
disappearingView.configureLayout { (layout) in
layout.isEnabled = true
layout.flexGrow = 1
}
contentView.addSubview(disappearingView)
button.setTitle("Add Blue View", for: .selected)
button.setTitle("Remove Blue View", for: .normal)
button.addTarget(self, action: #selector(buttonWasTapped), for: .touchUpInside)
button.configureLayout { (layout) in
layout.isEnabled = true
layout.height = 300
layout.width = 300
layout.alignSelf = .center
}
root.addSubview(button)
root.yoga.applyLayout(preservingOrigin: false)
}
// MARK - UIButton Action
@objc func buttonWasTapped() {
button.isSelected = !button.isSelected
button.isUserInteractionEnabled = false
disappearingView.yoga.isIncludedInLayout = !disappearingView.yoga.isIncludedInLayout
disappearingView.isHidden = !disappearingView.isHidden
contentView.yoga.applyLayout(preservingOrigin: true)
button.isUserInteractionEnabled = true
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import UIKit
import YogaKit
final class SingleLabelCollectionCell: UICollectionViewCell {
let label: UILabel = UILabel(frame: .zero)
override init(frame: CGRect) {
super.init(frame: frame)
contentView.configureLayout { (layout) in
layout.isEnabled = true
layout.flexDirection = .column
layout.justifyContent = .flexEnd
}
label.textAlignment = .center
label.numberOfLines = 1
label.yoga.isIncludedInLayout = false
contentView.addSubview(label)
let border = UIView(frame: .zero)
border.backgroundColor = .lightGray
border.configureLayout { (layout) in
layout.isEnabled = true
layout.height = 0.5
layout.marginHorizontal = 25
}
contentView.addSubview(border)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
contentView.yoga.applyLayout(preservingOrigin: false)
label.frame = contentView.bounds
}
}

21
android/README.md Normal file
View File

@@ -0,0 +1,21 @@
# YogaLayout [![Platform](https://img.shields.io/badge/platforms-Android-orange.svg)](https://facebook.github.io/yoga/docs/api/android/) [![Languages](https://img.shields.io/badge/languages-Java-orange.svg)](https://facebook.github.io/yoga/docs/api/android/) [![Download](https://img.shields.io/bintray/v/facebook/maven/com.facebook.yoga.android:yoga-layout.svg)](https://bintray.com/facebook/maven/com.facebook.yoga.android%3Ayoga-layout/_latestVersion)
## Installation
YogaLayout is available via jcenter:
implementation 'com.facebook.yoga.android:yoga-layout:1.16.0'
## Getting Started
Check out the docs [here](https://yogalayout.com/getting-started/standalone/).
We also have a sample project. To try it, clone the repo and run (with a device attached)
buck install -r android/sample
## Contributing
We welcome all pull-requests! At Facebook we sync the open source version of YogaKit daily, so we're always testing the latest changes.
See the CONTRIBUTING file for how to help out.

41
android/build.gradle Normal file
View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
plugins {
id("com.android.library")
id("publish")
}
android {
namespace 'com.facebook.yoga.android'
compileSdkVersion rootProject.compileSdkVersion
buildToolsVersion rootProject.buildToolsVersion
ndkVersion rootProject.ndkVersion
defaultConfig {
minSdkVersion rootProject.minSdkVersion
targetSdkVersion rootProject.targetSdkVersion
}
compileOptions {
targetCompatibility rootProject.targetCompatibilityVersion
sourceCompatibility rootProject.sourceCompatibilityVersion
}
publishing {
multipleVariants {
withSourcesJar()
withJavadocJar()
includeBuildTypeValues('debug', 'release')
}
}
}
dependencies {
api project(':yoga')
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) Facebook, Inc. and its affiliates.
This source code is licensed under the MIT license found in the
LICENSE file in the root directory of this source tree.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application/>
</manifest>

View File

@@ -0,0 +1,149 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.yoga.android;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.yoga.YogaNode;
import com.facebook.yoga.YogaNodeFactory;
/**
* Much like a {@link YogaLayout}, except this class does not render itself (the container) to the
* screen. As a result, <i>do not use this if you wish the container to have a background or
* foreground</i>. However, all of its children will still render as expected.
*
* <p>
* In practice, this class never added to the View tree, and all its children become children of its
* parent. As a result, all the layout (such as the traversal of the tree) is performed by Yoga
* (and so natively) increasing performance.
*/
public class VirtualYogaLayout extends ViewGroup {
final private List<View> mChildren = new LinkedList<>();
final private Map<View, YogaNode> mYogaNodes = new HashMap<>();
final private YogaNode mYogaNode = YogaNodeFactory.create();
public VirtualYogaLayout(Context context) {
super(context);
}
public VirtualYogaLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public VirtualYogaLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
YogaLayout.LayoutParams lp = new YogaLayout.LayoutParams(context, attrs);
YogaLayout.applyLayoutParams(lp, mYogaNode, this);
}
public YogaNode getYogaNode() {
return mYogaNode;
}
/**
* Called to add a view, creating a new yoga node for it and adding that yoga node to the parent.
* If the child is a {@link VirtualYogaLayout}, we simply transfer all its children to this one
* in a manner that maintains the tree, and add its root to the tree.
*
* @param child the View to add
* @param index the position at which to add it (ignored)
* @param params the layout parameters to apply
*/
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (child instanceof VirtualYogaLayout) {
((VirtualYogaLayout) child).transferChildren(this);
final YogaNode childNode = ((VirtualYogaLayout) child).getYogaNode();
mYogaNode.addChildAt(childNode, mYogaNode.getChildCount());
return;
}
YogaNode node = YogaNodeFactory.create();
YogaLayout.LayoutParams lp = new YogaLayout.LayoutParams(params);
YogaLayout.applyLayoutParams(lp, node, child);
node.setData(child);
node.setMeasureFunction(new YogaLayout.ViewMeasureFunction());
mYogaNode.addChildAt(node, mYogaNode.getChildCount());
addView(child, node);
}
/**
* Called to add a view with a corresponding node, but not to change the Yoga tree in any way.
*
* @param child the View to add
* @param node the corresponding yoga node
*/
public void addView(View child, YogaNode node) {
mChildren.add(child);
mYogaNodes.put(child, node);
}
/**
* Gives up children {@code View}s to the parent, maintaining the Yoga tree. This function calls
* {@link YogaLayout#addView(View, YogaNode)} or {@link VirtualYogaLayout#addView(View, YogaNode)}
* on the parent to add the {@code View} without generating new yoga nodes.
*
* @param parent the parent to pass children to (must be a YogaLayout or a VirtualYogaLayout)
*/
protected void transferChildren(ViewGroup parent) {
if (parent instanceof VirtualYogaLayout) {
for (View child : mChildren) {
((VirtualYogaLayout) parent).addView(child, mYogaNodes.get(child));
}
} else if (parent instanceof YogaLayout) {
for (View child : mChildren) {
((YogaLayout) parent).addView(child, mYogaNodes.get(child));
}
} else {
throw new RuntimeException("VirtualYogaLayout cannot transfer children to ViewGroup of type "
+parent.getClass().getCanonicalName()+". Must either be a VirtualYogaLayout or a " +
"YogaLayout.");
}
mChildren.clear();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
throw new RuntimeException("Attempting to layout a VirtualYogaLayout");
}
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new YogaLayout.LayoutParams(getContext(), attrs);
}
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new YogaLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new YogaLayout.LayoutParams(p);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof YogaLayout.LayoutParams;
}
}

View File

@@ -0,0 +1,816 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.yoga.android;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.yoga.YogaAlign;
import com.facebook.yoga.YogaConstants;
import com.facebook.yoga.YogaDirection;
import com.facebook.yoga.YogaDisplay;
import com.facebook.yoga.YogaEdge;
import com.facebook.yoga.YogaFlexDirection;
import com.facebook.yoga.YogaJustify;
import com.facebook.yoga.YogaMeasureFunction;
import com.facebook.yoga.YogaMeasureMode;
import com.facebook.yoga.YogaMeasureOutput;
import com.facebook.yoga.YogaNode;
import com.facebook.yoga.YogaNodeFactory;
import com.facebook.yoga.YogaOverflow;
import com.facebook.yoga.YogaPositionType;
import com.facebook.yoga.YogaWrap;
import java.util.HashMap;
import java.util.Map;
/**
* A {@code ViewGroup} based on the Yoga layout engine.
*
* <p>
* This class is designed to be as "plug and play" as possible. That is, you can use it in XML
* like this (note: to use {@code YogaLayout} you need to use the {@link YogaViewLayoutFactory}):
* <p>
* <pre>{@code
* <YogaLayout
* xmlns:android="http://schemas.android.com/apk/res/android"
* xmlns:yoga="http://schemas.android.com/apk/com.facebook.yoga.android"
* android:layout_width="match_owner"
* android:layout_height="match_owner"
* yoga:flex_direction="row"
* yoga:padding_all="10dp"
* >
* <TextView
* android:layout_width="match_owner"
* android:layout_height="match_owner"
* android:text="Hello, World!"
* yoga:flex="1"
* />
* </YogaLayout>
* }</pre>
*
* Under the hood, all views added to this {@code ViewGroup} are laid out using flexbox rules
* and the Yoga engine.
*/
public class YogaLayout extends ViewGroup {
private final Map<View, YogaNode> mYogaNodes;
private final YogaNode mYogaNode;
public YogaLayout(Context context) {
this(context, null, 0);
}
public YogaLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public YogaLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mYogaNode = YogaNodeFactory.create();
mYogaNodes = new HashMap<>();
mYogaNode.setData(this);
mYogaNode.setMeasureFunction(new ViewMeasureFunction());
LayoutParams layoutParams = null;
if (attrs != null) {
layoutParams = new LayoutParams(context, attrs);
} else {
layoutParams = (LayoutParams) generateDefaultLayoutParams();
}
applyLayoutParams(layoutParams, mYogaNode, this);
}
public YogaNode getYogaNode() {
return mYogaNode;
}
public YogaNode getYogaNodeForView(View view) {
return mYogaNodes.get(view);
}
/**
* Adds a child view with the specified layout parameters.
*
* In the typical View is added, this constructs a {@code YogaNode} for this child and applies all
* the {@code yoga:*} attributes. The Yoga node is added to the Yoga tree and the child is added
* to this ViewGroup.
*
* If the child is a {@link YogaLayout} itself, we do not construct a new Yoga node for that
* child, but use its root node instead.
*
* If the child is a {@link VirtualYogaLayout}, we also use its Yoga node, but we also instruct it
* to transfer all of its children to this {@link YogaLayout} while preserving the Yoga tree (so
* that the layout of its children is correct). The {@link VirtualYogaLayout} is then not added
* to the View hierarchy.
*
* <p><strong>Note:</strong> do not invoke this method from
* {@code #draw(android.graphics.Canvas)}, {@code onDraw(android.graphics.Canvas)},
* {@code #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
*
* @param child the child view to add
* @param index the position at which to add the child or -1 to add last
* @param params the layout parameters to set on the child
*/
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
// Internal nodes (which this is now) cannot have measure functions
mYogaNode.setMeasureFunction(null);
if (child instanceof VirtualYogaLayout) {
((VirtualYogaLayout) child).transferChildren(this);
final YogaNode childNode = ((VirtualYogaLayout) child).getYogaNode();
mYogaNode.addChildAt(childNode, mYogaNode.getChildCount());
return;
}
super.addView(child, index, params);
// It is possible that addView is being called as part of a transferal of children, in which
// case we already know about the YogaNode and only need the Android View tree to be aware
// that we now own this child. If so, we don't need to do anything further
if (mYogaNodes.containsKey(child)) {
return;
}
YogaNode childNode;
if (child instanceof YogaLayout) {
childNode = ((YogaLayout) child).getYogaNode();
} else {
if(mYogaNodes.containsKey(child)) {
childNode = mYogaNodes.get(child);
} else {
childNode = YogaNodeFactory.create();
}
childNode.setData(child);
childNode.setMeasureFunction(new ViewMeasureFunction());
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
applyLayoutParams(lp, childNode, child);
mYogaNodes.put(child, childNode);
mYogaNode.addChildAt(childNode, mYogaNode.getChildCount());
}
/**
* Adds a view to this {@code ViewGroup} with an already given {@code YogaNode}. Use
* this function if you already have a Yoga node (and perhaps tree) associated with the view you
* are adding, that you would like to preserve.
*
* @param child The view to add
* @param node The Yoga node belonging to the view
*/
public void addView(View child, YogaNode node) {
mYogaNodes.put(child, node);
addView(child);
}
@Override
public void removeView(View view) {
removeViewFromYogaTree(view, false);
super.removeView(view);
}
@Override
public void removeViewAt(int index) {
removeViewFromYogaTree(getChildAt(index), false);
super.removeViewAt(index);
}
@Override
public void removeViewInLayout(View view) {
removeViewFromYogaTree(view, true);
super.removeViewInLayout(view);
}
@Override
public void removeViews(int start, int count) {
for (int i = start; i < start + count; i++) {
removeViewFromYogaTree(getChildAt(i), false);
}
super.removeViews(start, count);
}
@Override
public void removeViewsInLayout(int start, int count) {
for (int i = start; i < start + count; i++) {
removeViewFromYogaTree(getChildAt(i), true);
}
super.removeViewsInLayout(start, count);
}
@Override
public void removeAllViews() {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
removeViewFromYogaTree(getChildAt(i), false);
}
super.removeAllViews();
}
@Override
public void removeAllViewsInLayout() {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
removeViewFromYogaTree(getChildAt(i), true);
}
super.removeAllViewsInLayout();
}
/**
* Marks a particular view as "dirty" and to be relaid out. If the view is not a child of this
* {@link YogaLayout}, the entire tree is traversed to find it.
*
* @param view the view to mark as dirty
*/
public void invalidate(View view) {
if (mYogaNodes.containsKey(view)) {
mYogaNodes.get(view).dirty();
return;
}
final int childCount = mYogaNode.getChildCount();
for (int i = 0; i < childCount; i++) {
final YogaNode yogaNode = mYogaNode.getChildAt(i);
if (yogaNode.getData() instanceof YogaLayout) {
((YogaLayout) yogaNode.getData()).invalidate(view);
}
}
invalidate();
}
private void removeViewFromYogaTree(View view, boolean inLayout) {
final YogaNode node = mYogaNodes.get(view);
if (node == null) {
return;
}
final YogaNode owner = node.getOwner();
for (int i = 0; i < owner.getChildCount(); i++) {
if (owner.getChildAt(i).equals(node)) {
owner.removeChildAt(i);
break;
}
}
node.setData(null);
mYogaNodes.remove(view);
if (inLayout) {
mYogaNode.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED);
}
}
private void applyLayoutRecursive(YogaNode node, float xOffset, float yOffset) {
View view = (View) node.getData();
if (view != null && view != this) {
if (view.getVisibility() == GONE) {
return;
}
int left = Math.round(xOffset + node.getLayoutX());
int top = Math.round(yOffset + node.getLayoutY());
view.measure(
View.MeasureSpec.makeMeasureSpec(
Math.round(node.getLayoutWidth()),
View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(
Math.round(node.getLayoutHeight()),
View.MeasureSpec.EXACTLY));
view.layout(left, top, left + view.getMeasuredWidth(), top + view.getMeasuredHeight());
}
final int childrenCount = node.getChildCount();
for (int i = 0; i < childrenCount; i++) {
if (this.equals(view)) {
applyLayoutRecursive(node.getChildAt(i), xOffset, yOffset);
} else if (view instanceof YogaLayout) {
continue;
} else {
applyLayoutRecursive(
node.getChildAt(i),
xOffset + node.getLayoutX(),
yOffset + node.getLayoutY());
}
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// Either we are a root of a tree, or this function is called by our owner's onLayout, in which
// case our r-l and b-t are the size of our node.
if (!(getParent() instanceof YogaLayout)) {
createLayout(
MeasureSpec.makeMeasureSpec(r - l, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(b - t, MeasureSpec.EXACTLY));
}
applyLayoutRecursive(mYogaNode, 0, 0);
}
/**
* This function is mostly unneeded, because Yoga is doing the measuring. Hence we only need to
* return accurate results if we are the root.
*
* @param widthMeasureSpec the suggested specification for the width
* @param heightMeasureSpec the suggested specification for the height
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (!(getParent() instanceof YogaLayout)) {
createLayout(widthMeasureSpec, heightMeasureSpec);
}
setMeasuredDimension(
Math.round(mYogaNode.getLayoutWidth()),
Math.round(mYogaNode.getLayoutHeight()));
}
private void createLayout(int widthMeasureSpec, int heightMeasureSpec) {
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode == MeasureSpec.EXACTLY) {
mYogaNode.setHeight(heightSize);
}
if (widthMode == MeasureSpec.EXACTLY) {
mYogaNode.setWidth(widthSize);
}
if (heightMode == MeasureSpec.AT_MOST) {
mYogaNode.setMaxHeight(heightSize);
}
if (widthMode == MeasureSpec.AT_MOST) {
mYogaNode.setMaxWidth(widthSize);
}
mYogaNode.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED);
}
/**
* Applies the layout parameters to the YogaNode. That is, this function is a translator from
* {@code yoga:X="Y"} to {@code yogaNode.setX(Y);}, with some reasonable defaults.
*
* <p>
* If the SDK version is high enough, and the {@code yoga:direction} is not set on
* the component, the direction (LTR or RTL) is set according to the locale.
*
* <p>
* The attributes {@code padding_top}, {@code padding_right} etc. default to those of the view's
* drawable background, if it has one.
*
* @param layoutParameters The source set of params
* @param node The destination node
*/
protected static void applyLayoutParams(LayoutParams layoutParameters, YogaNode node, View view) {
// JELLY_BEAN_MR1 (17) is the first version supporting getLayoutDirection()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
Configuration configuration = view.getResources().getConfiguration();
if (configuration.getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
node.setDirection(YogaDirection.RTL);
}
}
Drawable background = view.getBackground();
if (background != null) {
final Rect backgroundPadding = new Rect();
if (background.getPadding(backgroundPadding)) {
node.setPadding(YogaEdge.LEFT, backgroundPadding.left);
node.setPadding(YogaEdge.TOP, backgroundPadding.top);
node.setPadding(YogaEdge.RIGHT, backgroundPadding.right);
node.setPadding(YogaEdge.BOTTOM, backgroundPadding.bottom);
}
}
for (int i = 0; i < layoutParameters.numericAttributes.size(); i++) {
final int attribute = layoutParameters.numericAttributes.keyAt(i);
final float value = layoutParameters.numericAttributes.valueAt(i);
if (attribute == R.styleable.yoga_yg_alignContent) {
node.setAlignContent(YogaAlign.fromInt(Math.round(value)));
} else if (attribute == R.styleable.yoga_yg_alignItems) {
node.setAlignItems(YogaAlign.fromInt(Math.round(value)));
} else if (attribute == R.styleable.yoga_yg_alignSelf) {
node.setAlignSelf(YogaAlign.fromInt(Math.round(value)));
} else if (attribute == R.styleable.yoga_yg_aspectRatio) {
node.setAspectRatio(value);
} else if (attribute == R.styleable.yoga_yg_borderLeft) {
node.setBorder(YogaEdge.LEFT, value);
} else if (attribute == R.styleable.yoga_yg_borderTop) {
node.setBorder(YogaEdge.TOP, value);
} else if (attribute == R.styleable.yoga_yg_borderRight) {
node.setBorder(YogaEdge.RIGHT, value);
} else if (attribute == R.styleable.yoga_yg_borderBottom) {
node.setBorder(YogaEdge.BOTTOM, value);
} else if (attribute == R.styleable.yoga_yg_borderStart) {
node.setBorder(YogaEdge.START, value);
} else if (attribute == R.styleable.yoga_yg_borderEnd) {
node.setBorder(YogaEdge.END, value);
} else if (attribute == R.styleable.yoga_yg_borderHorizontal) {
node.setBorder(YogaEdge.HORIZONTAL, value);
} else if (attribute == R.styleable.yoga_yg_borderVertical) {
node.setBorder(YogaEdge.VERTICAL, value);
} else if (attribute == R.styleable.yoga_yg_borderAll) {
node.setBorder(YogaEdge.ALL, value);
} else if (attribute == R.styleable.yoga_yg_direction) {
node.setDirection(YogaDirection.fromInt(Math.round(value)));
} else if (attribute == R.styleable.yoga_yg_display) {
node.setDisplay(YogaDisplay.fromInt(Math.round(value)));
} else if (attribute == R.styleable.yoga_yg_flex) {
node.setFlex(value);
} else if (attribute == R.styleable.yoga_yg_flexBasis) {
node.setFlexBasis(value);
} else if (attribute == R.styleable.yoga_yg_flexDirection) {
node.setFlexDirection(YogaFlexDirection.fromInt(Math.round(value)));
} else if (attribute == R.styleable.yoga_yg_flexGrow) {
node.setFlexGrow(value);
} else if (attribute == R.styleable.yoga_yg_flexShrink) {
node.setFlexShrink(value);
} else if (attribute == R.styleable.yoga_yg_height) {
node.setHeight(value);
} else if (attribute == R.styleable.yoga_yg_marginLeft) {
node.setMargin(YogaEdge.LEFT, value);
} else if (attribute == R.styleable.yoga_yg_justifyContent) {
node.setJustifyContent(YogaJustify.fromInt(Math.round(value)));
} else if (attribute == R.styleable.yoga_yg_marginTop) {
node.setMargin(YogaEdge.TOP, value);
} else if (attribute == R.styleable.yoga_yg_marginRight) {
node.setMargin(YogaEdge.RIGHT, value);
} else if (attribute == R.styleable.yoga_yg_marginBottom) {
node.setMargin(YogaEdge.BOTTOM, value);
} else if (attribute == R.styleable.yoga_yg_marginStart) {
node.setMargin(YogaEdge.START, value);
} else if (attribute == R.styleable.yoga_yg_marginEnd) {
node.setMargin(YogaEdge.END, value);
} else if (attribute == R.styleable.yoga_yg_marginHorizontal) {
node.setMargin(YogaEdge.HORIZONTAL, value);
} else if (attribute == R.styleable.yoga_yg_marginVertical) {
node.setMargin(YogaEdge.VERTICAL, value);
} else if (attribute == R.styleable.yoga_yg_marginAll) {
node.setMargin(YogaEdge.ALL, value);
} else if (attribute == R.styleable.yoga_yg_maxHeight) {
node.setMaxHeight(value);
} else if (attribute == R.styleable.yoga_yg_maxWidth) {
node.setMaxWidth(value);
} else if (attribute == R.styleable.yoga_yg_minHeight) {
node.setMinHeight(value);
} else if (attribute == R.styleable.yoga_yg_minWidth) {
node.setMinWidth(value);
} else if (attribute == R.styleable.yoga_yg_overflow) {
node.setOverflow(YogaOverflow.fromInt(Math.round(value)));
} else if (attribute == R.styleable.yoga_yg_paddingLeft) {
node.setPadding(YogaEdge.LEFT, value);
} else if (attribute == R.styleable.yoga_yg_paddingTop) {
node.setPadding(YogaEdge.TOP, value);
} else if (attribute == R.styleable.yoga_yg_paddingRight) {
node.setPadding(YogaEdge.RIGHT, value);
} else if (attribute == R.styleable.yoga_yg_paddingBottom) {
node.setPadding(YogaEdge.BOTTOM, value);
} else if (attribute == R.styleable.yoga_yg_paddingStart) {
node.setPadding(YogaEdge.START, value);
} else if (attribute == R.styleable.yoga_yg_paddingEnd) {
node.setPadding(YogaEdge.END, value);
} else if (attribute == R.styleable.yoga_yg_paddingHorizontal) {
node.setPadding(YogaEdge.HORIZONTAL, value);
} else if (attribute == R.styleable.yoga_yg_paddingVertical) {
node.setPadding(YogaEdge.VERTICAL, value);
} else if (attribute == R.styleable.yoga_yg_paddingAll) {
node.setPadding(YogaEdge.ALL, value);
} else if (attribute == R.styleable.yoga_yg_positionLeft) {
node.setPosition(YogaEdge.LEFT, value);
} else if (attribute == R.styleable.yoga_yg_positionTop) {
node.setPosition(YogaEdge.TOP, value);
} else if (attribute == R.styleable.yoga_yg_positionRight) {
node.setPosition(YogaEdge.RIGHT, value);
} else if (attribute == R.styleable.yoga_yg_positionBottom) {
node.setPosition(YogaEdge.BOTTOM, value);
} else if (attribute == R.styleable.yoga_yg_positionStart) {
node.setPosition(YogaEdge.START, value);
} else if (attribute == R.styleable.yoga_yg_positionEnd) {
node.setPosition(YogaEdge.END, value);
} else if (attribute == R.styleable.yoga_yg_positionHorizontal) {
node.setPosition(YogaEdge.HORIZONTAL, value);
} else if (attribute == R.styleable.yoga_yg_positionVertical) {
node.setPosition(YogaEdge.VERTICAL, value);
} else if (attribute == R.styleable.yoga_yg_positionAll) {
node.setPosition(YogaEdge.ALL, value);
} else if (attribute == R.styleable.yoga_yg_positionType) {
node.setPositionType(YogaPositionType.fromInt(Math.round(value)));
} else if (attribute == R.styleable.yoga_yg_width) {
node.setWidth(value);
} else if (attribute == R.styleable.yoga_yg_wrap) {
node.setWrap(YogaWrap.fromInt(Math.round(value)));
}
}
for (int i = 0; i < layoutParameters.stringAttributes.size(); i++) {
final int attribute = layoutParameters.stringAttributes.keyAt(i);
final String value = layoutParameters.stringAttributes.valueAt(i);
if (value.equals("auto")) {
if (attribute == R.styleable.yoga_yg_marginLeft) {
node.setMarginAuto(YogaEdge.LEFT);
} else if (attribute == R.styleable.yoga_yg_marginTop) {
node.setMarginAuto(YogaEdge.TOP);
} else if (attribute == R.styleable.yoga_yg_marginRight) {
node.setMarginAuto(YogaEdge.RIGHT);
} else if (attribute == R.styleable.yoga_yg_marginBottom) {
node.setMarginAuto(YogaEdge.BOTTOM);
} else if (attribute == R.styleable.yoga_yg_marginStart) {
node.setMarginAuto(YogaEdge.START);
} else if (attribute == R.styleable.yoga_yg_marginEnd) {
node.setMarginAuto(YogaEdge.END);
} else if (attribute == R.styleable.yoga_yg_marginHorizontal) {
node.setMarginAuto(YogaEdge.HORIZONTAL);
} else if (attribute == R.styleable.yoga_yg_marginVertical) {
node.setMarginAuto(YogaEdge.VERTICAL);
} else if (attribute == R.styleable.yoga_yg_marginAll) {
node.setMarginAuto(YogaEdge.ALL);
}
}
if (value.endsWith("%")) {
final float numericValue = Float.parseFloat(value.substring(0, value.length()-1));
if (attribute == R.styleable.yoga_yg_flexBasis) {
node.setFlexBasisPercent(numericValue);
} else if (attribute == R.styleable.yoga_yg_height) {
node.setHeightPercent(numericValue);
} else if (attribute == R.styleable.yoga_yg_marginLeft) {
node.setMarginPercent(YogaEdge.LEFT, numericValue);
} else if (attribute == R.styleable.yoga_yg_marginTop) {
node.setMarginPercent(YogaEdge.TOP, numericValue);
} else if (attribute == R.styleable.yoga_yg_marginRight) {
node.setMarginPercent(YogaEdge.RIGHT, numericValue);
} else if (attribute == R.styleable.yoga_yg_marginBottom) {
node.setMarginPercent(YogaEdge.BOTTOM, numericValue);
} else if (attribute == R.styleable.yoga_yg_marginStart) {
node.setMarginPercent(YogaEdge.START, numericValue);
} else if (attribute == R.styleable.yoga_yg_marginEnd) {
node.setMarginPercent(YogaEdge.END, numericValue);
} else if (attribute == R.styleable.yoga_yg_marginHorizontal) {
node.setMarginPercent(YogaEdge.HORIZONTAL, numericValue);
} else if (attribute == R.styleable.yoga_yg_marginVertical) {
node.setMarginPercent(YogaEdge.VERTICAL, numericValue);
} else if (attribute == R.styleable.yoga_yg_marginAll) {
node.setMarginPercent(YogaEdge.ALL, numericValue);
} else if (attribute == R.styleable.yoga_yg_maxHeight) {
node.setMaxHeightPercent(numericValue);
} else if (attribute == R.styleable.yoga_yg_maxWidth) {
node.setMaxWidthPercent(numericValue);
} else if (attribute == R.styleable.yoga_yg_minHeight) {
node.setMinHeightPercent(numericValue);
} else if (attribute == R.styleable.yoga_yg_minWidth) {
node.setMinWidthPercent(numericValue);
} else if (attribute == R.styleable.yoga_yg_paddingLeft) {
node.setPaddingPercent(YogaEdge.LEFT, numericValue);
} else if (attribute == R.styleable.yoga_yg_paddingTop) {
node.setPaddingPercent(YogaEdge.TOP, numericValue);
} else if (attribute == R.styleable.yoga_yg_paddingRight) {
node.setPaddingPercent(YogaEdge.RIGHT, numericValue);
} else if (attribute == R.styleable.yoga_yg_paddingBottom) {
node.setPaddingPercent(YogaEdge.BOTTOM, numericValue);
} else if (attribute == R.styleable.yoga_yg_paddingStart) {
node.setPaddingPercent(YogaEdge.START, numericValue);
} else if (attribute == R.styleable.yoga_yg_paddingEnd) {
node.setPaddingPercent(YogaEdge.END, numericValue);
} else if (attribute == R.styleable.yoga_yg_paddingHorizontal) {
node.setPaddingPercent(YogaEdge.HORIZONTAL, numericValue);
} else if (attribute == R.styleable.yoga_yg_paddingVertical) {
node.setPaddingPercent(YogaEdge.VERTICAL, numericValue);
} else if (attribute == R.styleable.yoga_yg_paddingAll) {
node.setPaddingPercent(YogaEdge.ALL, numericValue);
} else if (attribute == R.styleable.yoga_yg_positionLeft) {
node.setPositionPercent(YogaEdge.LEFT, numericValue);
} else if (attribute == R.styleable.yoga_yg_positionTop) {
node.setPositionPercent(YogaEdge.TOP, numericValue);
} else if (attribute == R.styleable.yoga_yg_positionRight) {
node.setPositionPercent(YogaEdge.RIGHT, numericValue);
} else if (attribute == R.styleable.yoga_yg_positionBottom) {
node.setPositionPercent(YogaEdge.BOTTOM, numericValue);
} else if (attribute == R.styleable.yoga_yg_positionStart) {
node.setPositionPercent(YogaEdge.START, numericValue);
} else if (attribute == R.styleable.yoga_yg_positionEnd) {
node.setPositionPercent(YogaEdge.END, numericValue);
} else if (attribute == R.styleable.yoga_yg_positionHorizontal) {
node.setPositionPercent(YogaEdge.HORIZONTAL, numericValue);
} else if (attribute == R.styleable.yoga_yg_positionVertical) {
node.setPositionPercent(YogaEdge.VERTICAL, numericValue);
} else if (attribute == R.styleable.yoga_yg_positionAll) {
node.setPositionPercent(YogaEdge.ALL, numericValue);
} else if (attribute == R.styleable.yoga_yg_width) {
node.setWidthPercent(numericValue);
}
}
}
}
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new YogaLayout.LayoutParams(getContext(), attrs);
}
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new YogaLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new YogaLayout.LayoutParams(p);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}
/**
* {@code YogaLayout.LayoutParams} are used by views to tell {@link YogaLayout} how they want to
* be laid out. More precisely, the specify the yoga parameters of the view.
*
* <p>
* This is actually mostly a wrapper around a {@code SparseArray} that holds a mapping between
* styleable id's ({@code R.styleable.yoga_yg_*}) and the float of their values. In cases where
* the value is an enum or an integer, they should first be cast to int (with rounding) before
* using.
*/
public static class LayoutParams extends ViewGroup.LayoutParams {
/**
* A mapping from attribute keys ({@code R.styleable.yoga_yg_*}) to the float of their values.
* For attributes like position_percent_left (float), this is the native type. For attributes
* like align_self (enums), the integer enum value is cast (rounding is used on the other side
* to prevent precision errors). Dimension attributes are stored as float pixels.
*/
SparseArray<Float> numericAttributes;
/**
* A mapping from attribute keys ({@code R.styleable.yoga_yg_*}) with string values to those
* strings. This is used for values such as "auto".
*/
SparseArray<String> stringAttributes;
/**
* Constructs a set of layout params from a source set. In the case that the source set is
* actually a {@link YogaLayout.LayoutParams}, we can copy all the yoga attributes. Otherwise
* we start with a blank slate.
*
* @param source The layout params to copy from
*/
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
if (source instanceof LayoutParams) {
numericAttributes = ((LayoutParams) source).numericAttributes.clone();
stringAttributes = ((LayoutParams) source).stringAttributes.clone();
} else {
numericAttributes = new SparseArray<>();
stringAttributes = new SparseArray<>();
// Negative values include MATCH_PARENT and WRAP_CONTENT
if (source.width >= 0) {
numericAttributes.put(R.styleable.yoga_yg_width, (float) width);
}
if (source.height >= 0) {
numericAttributes.put(R.styleable.yoga_yg_height, (float) height);
}
}
}
/**
* Constructs a set of layout params, given width and height specs. In this case, we can set
* the {@code yoga:width} and {@code yoga:height} if we are given them explicitly. If other
* options (such as {@code match_owner} or {@code wrap_content} are given, then the owner
* LayoutParams will store them, and we deal with them during layout. (see
* {@link YogaLayout#createLayout})
*
* @param width the requested width, either a pixel size, {@code WRAP_CONTENT} or
* {@code MATCH_PARENT}.
* @param height the requested height, either a pixel size, {@code WRAP_CONTENT} or
* {@code MATCH_PARENT}.
*/
public LayoutParams(int width, int height) {
super(width, height);
numericAttributes = new SparseArray<>();
stringAttributes = new SparseArray<>();
// Negative values include MATCH_PARENT and WRAP_CONTENT
if (width >= 0) {
numericAttributes.put(R.styleable.yoga_yg_width, (float) width);
}
if (height >= 0) {
numericAttributes.put(R.styleable.yoga_yg_height, (float) height);
}
}
/**
* Constructs a set of layout params, given attributes. Grabs all the {@code yoga:*}
* defined in {@code ALL_YOGA_ATTRIBUTES} and collects the ones that are set in {@code attrs}.
*
* @param context the application environment
* @param attrs the set of attributes from which to extract the yoga specific attributes
*/
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
numericAttributes = new SparseArray<>();
stringAttributes = new SparseArray<>();
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.yoga);
// Negative values include MATCH_PARENT and WRAP_CONTENT
if (width >= 0) {
numericAttributes.put(R.styleable.yoga_yg_width, (float) width);
}
if (height >= 0) {
numericAttributes.put(R.styleable.yoga_yg_height, (float) height);
}
final int attributeCount = a.getIndexCount();
for (int i = 0; i < attributeCount; i++) {
final int attribute = a.getIndex(i);
final TypedValue val = new TypedValue();
a.getValue(attribute, val);
if (val.type == TypedValue.TYPE_DIMENSION) {
numericAttributes.put(
attribute,
(float) a.getDimensionPixelSize(attribute, 0));
} else if (val.type == TypedValue.TYPE_STRING) {
stringAttributes.put(attribute, a.getString(attribute));
} else {
numericAttributes.put(attribute, a.getFloat(attribute, 0));
}
}
a.recycle();
}
}
/**
* Wrapper around measure function for yoga leaves.
*/
public static class ViewMeasureFunction implements YogaMeasureFunction {
/**
* A function to measure leaves of the Yoga tree. Yoga needs some way to know how large
* elements want to be. This function passes that question directly through to the relevant
* {@code View}'s measure function.
*
* @param node The yoga node to measure
* @param width The suggested width from the owner
* @param widthMode The type of suggestion for the width
* @param height The suggested height from the owner
* @param heightMode The type of suggestion for the height
* @return A measurement output ({@code YogaMeasureOutput}) for the node
*/
public long measure(
YogaNode node,
float width,
YogaMeasureMode widthMode,
float height,
YogaMeasureMode heightMode) {
final View view = (View) node.getData();
if (view == null || view instanceof YogaLayout) {
return YogaMeasureOutput.make(0, 0);
}
final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(
(int) width,
viewMeasureSpecFromYogaMeasureMode(widthMode));
final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(
(int) height,
viewMeasureSpecFromYogaMeasureMode(heightMode));
view.measure(widthMeasureSpec, heightMeasureSpec);
return YogaMeasureOutput.make(view.getMeasuredWidth(), view.getMeasuredHeight());
}
private int viewMeasureSpecFromYogaMeasureMode(YogaMeasureMode mode) {
if (mode == YogaMeasureMode.AT_MOST) {
return MeasureSpec.AT_MOST;
} else if (mode == YogaMeasureMode.EXACTLY) {
return MeasureSpec.EXACTLY;
} else {
return MeasureSpec.UNSPECIFIED;
}
}
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.yoga.android;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
/**
* A layout inflater factory. This provides our custom {@link YogaViewLayoutFactory#onCreateView}
* to the XML inflation system, allowing us to replace XML tags.
*/
public class YogaViewLayoutFactory implements LayoutInflater.Factory {
private static YogaViewLayoutFactory sYogaViewLayoutFactory;
/**
* Obtains (and initialises if necessary) the singleton {@link YogaViewLayoutFactory}.
*
* @return The singleton instance
*/
public static YogaViewLayoutFactory getInstance() {
if (sYogaViewLayoutFactory == null) {
sYogaViewLayoutFactory = new YogaViewLayoutFactory();
}
return sYogaViewLayoutFactory;
}
YogaViewLayoutFactory() {}
/**
* Hook for inflating from a LayoutInflater. This hook replaces the cumbersome
* {@code com.facebook.etc.YogaLayout} with simply {@code YogaLayout} in your XML and the same
* with {@code VirtualYogaLayout}.
*
* @param name Tag name to be inflated.
* @param context The context the view is being created in.
* @param attrs Inflation attributes as specified in XML file.
*
* @return View Newly created view. Return null for the default behavior.
*/
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
if (YogaLayout.class.getSimpleName().equals(name)) {
return new YogaLayout(context, attrs);
}
if (VirtualYogaLayout.class.getSimpleName().equals(name)) {
return new VirtualYogaLayout(context, attrs);
}
return null;
}
}

View File

@@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright (c) Facebook, Inc. and its affiliates.
This source code is licensed under the MIT license found in the
LICENSE file in the root directory of this source tree.
-->
<resources>
<declare-styleable name="yoga">
<attr name="yg_alignContent" format="enum">
<enum name="auto" value="0"/>
<enum name="flex_start" value="1"/>
<enum name="center" value="2"/>
<enum name="flex_end" value="3"/>
<enum name="stretch" value="4"/>
<enum name="baseline" value="5"/>
</attr>
<attr name="yg_alignItems" format="enum">
<enum name="auto" value="0"/>
<enum name="flex_start" value="1"/>
<enum name="center" value="2"/>
<enum name="flex_end" value="3"/>
<enum name="stretch" value="4"/>
<enum name="baseline" value="5"/>
</attr>
<attr name="yg_alignSelf" format="enum">
<enum name="auto" value="0"/>
<enum name="flex_start" value="1"/>
<enum name="center" value="2"/>
<enum name="flex_end" value="3"/>
<enum name="stretch" value="4"/>
<enum name="baseline" value="5"/>
</attr>
<attr name="yg_aspectRatio" format="float"/>
<attr name="yg_borderLeft" format="dimension"/>
<attr name="yg_borderTop" format="dimension"/>
<attr name="yg_borderRight" format="dimension"/>
<attr name="yg_borderBottom" format="dimension"/>
<attr name="yg_borderStart" format="dimension"/>
<attr name="yg_borderEnd" format="dimension"/>
<attr name="yg_borderHorizontal" format="dimension"/>
<attr name="yg_borderVertical" format="dimension"/>
<attr name="yg_borderAll" format="dimension"/>
<attr name="yg_direction" format="enum">
<enum name="inherit" value="0"/>
<enum name="ltr" value="1"/>
<enum name="rtl" value="2"/>
</attr>
<attr name="yg_display" format="enum">
<enum name="flex" value="0"/>
<enum name="none" value="1"/>
</attr>
<attr name="yg_flex" format="float"/>
<attr name="yg_flexBasis" format="float|string"/>
<attr name="yg_flexDirection" format="enum">
<enum name="column" value="0"/>
<enum name="column_reverse" value="1"/>
<enum name="row" value="2"/>
<enum name="row_reverse" value="3"/>
</attr>
<attr name="yg_flexGrow" format="float"/>
<attr name="yg_flexShrink" format="float"/>
<attr name="yg_height" format="dimension|string"/>
<attr name="yg_justifyContent" format="enum">
<enum name="flex_start" value="0"/>
<enum name="center" value="1"/>
<enum name="flex_end" value="2"/>
<enum name="space_between" value="3"/>
<enum name="space_around" value="4"/>
</attr>
<attr name="yg_marginLeft" format="dimension|string"/>
<attr name="yg_marginTop" format="dimension|string"/>
<attr name="yg_marginRight" format="dimension|string"/>
<attr name="yg_marginBottom" format="dimension|string"/>
<attr name="yg_marginStart" format="dimension|string"/>
<attr name="yg_marginEnd" format="dimension|string"/>
<attr name="yg_marginHorizontal" format="dimension|string"/>
<attr name="yg_marginVertical" format="dimension|string"/>
<attr name="yg_marginAll" format="dimension|string"/>
<attr name="yg_maxHeight" format="dimension|string"/>
<attr name="yg_maxWidth" format="dimension|string"/>
<attr name="yg_minHeight" format="dimension|string"/>
<attr name="yg_minWidth" format="dimension|string"/>
<attr name="yg_overflow" format="enum">
<enum name="visible" value="0"/>
<enum name="hidden" value="1"/>
<enum name="scroll" value="2"/>
</attr>
<attr name="yg_paddingLeft" format="dimension|string"/>
<attr name="yg_paddingTop" format="dimension|string"/>
<attr name="yg_paddingRight" format="dimension|string"/>
<attr name="yg_paddingBottom" format="dimension|string"/>
<attr name="yg_paddingStart" format="dimension|string"/>
<attr name="yg_paddingEnd" format="dimension|string"/>
<attr name="yg_paddingHorizontal" format="dimension|string"/>
<attr name="yg_paddingVertical" format="dimension|string"/>
<attr name="yg_paddingAll" format="dimension|string"/>
<attr name="yg_positionLeft" format="dimension|string"/>
<attr name="yg_positionTop" format="dimension|string"/>
<attr name="yg_positionRight" format="dimension|string"/>
<attr name="yg_positionBottom" format="dimension|string"/>
<attr name="yg_positionStart" format="dimension|string"/>
<attr name="yg_positionEnd" format="dimension|string"/>
<attr name="yg_positionHorizontal" format="dimension|string"/>
<attr name="yg_positionVertical" format="dimension|string"/>
<attr name="yg_positionAll" format="dimension|string"/>
<attr name="yg_positionType" format="enum">
<!-- "static" is a reserved keyword
<enum name="static" value="0"/>
-->
<enum name="relative" value="1"/>
<enum name="absolute" value="2"/>
<enum name="position_static" value="0"/>
<enum name="position_relative" value="1"/>
<enum name="position_absolute" value="2"/>
</attr>
<attr name="yg_width" format="dimension|string"/>
<attr name="yg_wrap" format="enum">
<enum name="no_wrap" value="0"/>
<enum name="wrap" value="1"/>
</attr>
</declare-styleable>
</resources>

View File

@@ -1,400 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <chrono>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <thread>
#include <benchmark/Benchmark.h>
#include <benchmark/TreeDeserialization.h>
#include <capture/CaptureTree.h>
#include <nlohmann/json.hpp>
namespace facebook::yoga {
using namespace nlohmann;
using namespace std::chrono;
constexpr uint32_t kNumRepetitions = 100;
using SteadyClockDurations =
std::array<steady_clock::duration, kNumRepetitions>;
static bool inputsMatch(
float actualWidth,
float expectedWidth,
float actualHeight,
float expectedHeight,
YGMeasureMode actualWidthMode,
YGMeasureMode expectedWidthMode,
YGMeasureMode actualHeightMode,
YGMeasureMode expectedHeightMode) {
bool widthsAreUndefined =
YGFloatIsUndefined(actualWidth) && YGFloatIsUndefined(expectedWidth);
bool widthsAreEqual = widthsAreUndefined || actualWidth == expectedWidth;
bool heightsAreUndefined =
YGFloatIsUndefined(actualHeight) && YGFloatIsUndefined(expectedHeight);
bool heightsAreEqual = heightsAreUndefined || actualHeight == expectedHeight;
return widthsAreEqual && heightsAreEqual &&
actualWidthMode == expectedWidthMode &&
actualHeightMode == expectedHeightMode;
}
YGSize defaultMeasureFunctionResult() {
std::cout << "Trying to measure a node that wasn't serialized" << std::endl;
return {10.0, 10.0};
}
YGSize mockMeasureFunc(
YGNodeConstRef node,
float availableWidth,
YGMeasureMode widthMode,
float availableHeight,
YGMeasureMode heightMode) {
(void)node;
auto fnsPtr = static_cast<SerializedMeasureFuncMap*>(YGNodeGetContext(node));
if (fnsPtr == nullptr) {
return defaultMeasureFunctionResult();
}
auto fnsIt = fnsPtr->find(node);
if (fnsIt == fnsPtr->end()) {
return defaultMeasureFunctionResult();
}
for (auto measureFunc : fnsIt->second) {
if (inputsMatch(
availableWidth,
measureFunc.inputWidth,
availableHeight,
measureFunc.inputHeight,
widthMode,
measureFunc.widthMode,
heightMode,
measureFunc.heightMode)) {
std::this_thread::sleep_for(
std::chrono::nanoseconds(measureFunc.durationNs));
return {measureFunc.outputWidth, measureFunc.outputHeight};
}
}
return defaultMeasureFunctionResult();
}
std::shared_ptr<const YGConfig> buildConfigFromJson(const json& j) {
json jsonConfig = j["config"];
std::shared_ptr<YGConfig> config(YGConfigNew(), YGConfigFree);
for (json::iterator it = jsonConfig.begin(); it != jsonConfig.end(); it++) {
if (it.key() == "use-web-defaults") {
YGConfigSetUseWebDefaults(config.get(), it.value());
} else if (it.key() == "point-scale-factor") {
YGConfigSetPointScaleFactor(config.get(), it.value());
} else if (it.key() == "errata") {
YGConfigSetErrata(config.get(), errataFromString(it.value()));
} else if (it.key() == "experimental-features") {
// Experimental features is serialized into an array where the values
// present indicate that that feature is enabled
for (json::iterator efIt = it.value().begin(); efIt != it.value().end();
efIt++) {
YGConfigSetExperimentalFeatureEnabled(
config.get(), experimentalFeatureFromString(efIt.value()), true);
}
}
}
return config;
}
std::string edgeStringFromPropertyName(
const std::string& key,
const std::string& propertyName) {
return key.substr(propertyName.length() + 1);
}
void setStylesFromJson(const json& j, YGNodeRef node) {
json style = j["style"];
for (const auto& [key, value] : style.items()) {
if (key == "flex-direction") {
YGNodeStyleSetFlexDirection(node, flexDirectionFromString(value));
} else if (key == "justify-content") {
YGNodeStyleSetJustifyContent(node, justifyContentFromString(value));
} else if (key == "align-items") {
YGNodeStyleSetAlignItems(node, alignFromString(value));
} else if (key == "align-content") {
YGNodeStyleSetAlignContent(node, alignFromString(value));
} else if (key == "align-self") {
YGNodeStyleSetAlignSelf(node, alignFromString(value));
} else if (key == "flex-wrap") {
YGNodeStyleSetFlexWrap(node, wrapFromString(value));
} else if (key == "overflow") {
YGNodeStyleSetOverflow(node, overflowFromString(value));
} else if (key == "display") {
YGNodeStyleSetDisplay(node, displayFromString(value));
} else if (key == "position-type") {
YGNodeStyleSetPositionType(node, positionTypeFromString(value));
} else if (key == "flex-grow") {
YGNodeStyleSetFlexGrow(node, value);
} else if (key == "flex-shrink") {
YGNodeStyleSetFlexShrink(node, value);
} else if (key == "flex") {
YGNodeStyleSetFlex(node, value);
} else if (key == "flex-basis") {
YGUnit unit = unitFromJson(value);
if (unit == YGUnitAuto) {
YGNodeStyleSetFlexBasisAuto(node);
} else if (unit == YGUnitPoint) {
YGNodeStyleSetFlexBasis(node, value["value"]);
} else if (unit == YGUnitPercent) {
YGNodeStyleSetFlexBasisPercent(node, value["value"]);
}
} else if (key.starts_with("position")) {
YGEdge edge = edgeFromString(edgeStringFromPropertyName(key, "position"));
YGUnit unit = unitFromJson(value);
if (unit == YGUnitPoint) {
YGNodeStyleSetPosition(node, edge, value["value"]);
} else if (unit == YGUnitPercent) {
YGNodeStyleSetPositionPercent(node, edge, value["value"]);
}
} else if (key.starts_with("padding")) {
YGEdge edge = edgeFromString(edgeStringFromPropertyName(key, "padding"));
YGUnit unit = unitFromJson(value);
if (unit == YGUnitPoint) {
YGNodeStyleSetPadding(node, edge, value["value"]);
} else if (unit == YGUnitPercent) {
YGNodeStyleSetPaddingPercent(node, edge, value["value"]);
}
} else if (key.starts_with("border")) {
YGEdge edge = edgeFromString(edgeStringFromPropertyName(key, "border"));
YGUnit unit = unitFromJson(value);
if (unit == YGUnitPoint) {
YGNodeStyleSetBorder(node, edge, value["value"]);
}
} else if (key.starts_with("margin")) {
YGEdge edge = edgeFromString(edgeStringFromPropertyName(key, "margin"));
YGUnit unit = unitFromJson(value);
if (unit == YGUnitPoint) {
YGNodeStyleSetMargin(node, edge, value["value"]);
} else if (unit == YGUnitPercent) {
YGNodeStyleSetMarginPercent(node, edge, value["value"]);
} else if (unit == YGUnitAuto) {
YGNodeStyleSetMarginAuto(node, edge);
}
} else if (key == "gap") {
YGUnit unit = unitFromJson(value);
if (unit == YGUnitPoint) {
YGNodeStyleSetGap(node, YGGutterAll, value["value"]);
}
} else if (key == "column-gap") {
YGUnit unit = unitFromJson(value);
if (unit == YGUnitPoint) {
YGNodeStyleSetGap(node, YGGutterColumn, value["value"]);
}
} else if (key == "row-gap") {
YGUnit unit = unitFromJson(value);
if (unit == YGUnitPoint) {
YGNodeStyleSetGap(node, YGGutterRow, value["value"]);
}
} else if (key == "height") {
YGUnit unit = unitFromJson(value);
if (unit == YGUnitAuto) {
YGNodeStyleSetHeightAuto(node);
} else if (unit == YGUnitPoint) {
YGNodeStyleSetHeight(node, value["value"]);
} else if (unit == YGUnitPercent) {
YGNodeStyleSetHeightPercent(node, value["value"]);
}
} else if (key == "width") {
YGUnit unit = unitFromJson(value);
if (unit == YGUnitAuto) {
YGNodeStyleSetWidthAuto(node);
} else if (unit == YGUnitPoint) {
YGNodeStyleSetWidth(node, value["value"]);
} else if (unit == YGUnitPercent) {
YGNodeStyleSetWidthPercent(node, value["value"]);
}
} else if (key == "min-height") {
YGUnit unit = unitFromJson(value);
if (unit == YGUnitPoint) {
YGNodeStyleSetMinHeight(node, value["value"]);
} else if (unit == YGUnitPercent) {
YGNodeStyleSetMinHeightPercent(node, value["value"]);
}
} else if (key == "min-width") {
YGUnit unit = unitFromJson(value);
if (unit == YGUnitPoint) {
YGNodeStyleSetMinWidth(node, value["value"]);
} else if (unit == YGUnitPercent) {
YGNodeStyleSetMinWidthPercent(node, value["value"]);
}
} else if (key == "max-height") {
YGUnit unit = unitFromJson(value);
if (unit == YGUnitPoint) {
YGNodeStyleSetMaxHeight(node, value["value"]);
} else if (unit == YGUnitPercent) {
YGNodeStyleSetMaxHeightPercent(node, value["value"]);
}
} else if (key == "max-width") {
YGUnit unit = unitFromJson(value);
if (unit == YGUnitPoint) {
YGNodeStyleSetMaxWidth(node, value["value"]);
} else if (unit == YGUnitPercent) {
YGNodeStyleSetMaxWidthPercent(node, value["value"]);
}
}
}
}
std::shared_ptr<YGNode> buildNodeFromJson(
const json& j,
std::shared_ptr<const YGConfig> config,
std::shared_ptr<SerializedMeasureFuncMap> fns) {
std::shared_ptr<YGNode> node(YGNodeNewWithConfig(config.get()), YGNodeFree);
if (!j.contains("node") || j["node"].is_null()) {
return node;
}
json nodeState = j["node"];
for (json::iterator it = nodeState.begin(); it != nodeState.end(); it++) {
if (it.key() == "always-forms-containing-block") {
YGNodeSetAlwaysFormsContainingBlock(node.get(), it.value());
} else if (it.key() == "measure-funcs") {
std::vector<SerializedMeasureFunc> vec{};
for (auto measureFuncJson : it.value()) {
vec.push_back(serializedMeasureFuncFromJson(measureFuncJson));
}
fns->insert(std::make_pair(node.get(), vec));
YGNodeSetContext(node.get(), it.value().is_null() ? nullptr : fns.get());
YGNodeSetMeasureFunc(node.get(), mockMeasureFunc);
}
}
return node;
}
std::shared_ptr<YogaNodeAndConfig> buildTreeFromJson(
const json& j,
std::shared_ptr<SerializedMeasureFuncMap> fns,
std::shared_ptr<YogaNodeAndConfig> parent,
size_t index) {
auto config = buildConfigFromJson(j);
auto node = buildNodeFromJson(j, config, fns);
auto wrapper = std::make_shared<YogaNodeAndConfig>(
node, config, std::vector<std::shared_ptr<YogaNodeAndConfig>>{});
if (parent != nullptr) {
YGNodeInsertChild(parent->node_.get(), node.get(), index);
parent->children_.push_back(wrapper);
}
setStylesFromJson(j, node.get());
if (j.contains("children")) {
json children = j["children"];
size_t childIndex = 0;
for (const json& child : children) {
buildTreeFromJson(child, fns, wrapper, childIndex);
childIndex++;
}
}
return wrapper;
}
BenchmarkResult generateBenchmark(json& capture) {
auto fns = std::make_shared<SerializedMeasureFuncMap>();
auto treeCreationBegin = steady_clock::now();
std::shared_ptr<YogaNodeAndConfig> root =
buildTreeFromJson(capture["tree"], fns, nullptr, 0 /*index*/);
auto treeCreationEnd = steady_clock::now();
json layoutInputs = capture["layout-inputs"];
float availableWidth = layoutInputs["available-width"];
float availableHeight = layoutInputs["available-height"];
YGDirection direction = directionFromString(layoutInputs["owner-direction"]);
auto layoutBegin = steady_clock::now();
YGNodeCalculateLayout(
root->node_.get(), availableWidth, availableHeight, direction);
auto layoutEnd = steady_clock::now();
return BenchmarkResult{
treeCreationEnd - treeCreationBegin, layoutEnd - layoutBegin};
}
static void printBenchmarkResult(
const std::string& name,
SteadyClockDurations& durations) {
std::array<double, kNumRepetitions> timesInMs{};
double mean = 0;
for (uint32_t i = 0; i < kNumRepetitions; i++) {
auto ms = duration<double, std::milli>(durations[i]).count();
timesInMs[i] = ms;
mean += ms;
}
mean /= kNumRepetitions;
std::sort(timesInMs.begin(), timesInMs.end());
double median = timesInMs[kNumRepetitions / 2];
double variance = 0;
for (uint32_t i = 0; i < kNumRepetitions; i++) {
variance += std::pow(timesInMs[i] - mean, 2);
}
variance /= kNumRepetitions;
double stddev = std::sqrt(variance);
printf("%s: median: %lf ms, stddev: %lf ms\n", name.c_str(), median, stddev);
}
void benchmark(std::filesystem::path& capturesDir) {
for (auto& capture : std::filesystem::directory_iterator(capturesDir)) {
if (capture.is_directory() || capture.path().extension() != ".json") {
continue;
}
SteadyClockDurations treeCreationDurations;
SteadyClockDurations layoutDurations;
SteadyClockDurations totalDurations;
std::ifstream captureFile(capture.path());
json j = json::parse(captureFile);
std::string captureName = capture.path().stem().string();
std::cout << "Starting benchmark for " << captureName << std::endl;
for (uint32_t i = 0; i < kNumRepetitions; i++) {
BenchmarkResult result = generateBenchmark(j);
treeCreationDurations[i] = result.treeCreationDuration;
layoutDurations[i] = result.layoutDuration;
totalDurations[i] = result.treeCreationDuration + result.layoutDuration;
}
printBenchmarkResult(captureName + " tree creation", treeCreationDurations);
printBenchmarkResult(captureName + " layout", layoutDurations);
printBenchmarkResult(captureName + " total", totalDurations);
std::cout << std::endl;
}
}
} // namespace facebook::yoga
int main(int argc, char* argv[]) {
if (argc == 2) {
std::filesystem::path capturesDir = argv[argc - 1];
facebook::yoga::benchmark(capturesDir);
} else {
throw std::invalid_argument("Expecting a path as an argument");
return 1;
}
return 0;
}

View File

@@ -1,39 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <chrono>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <yoga/Yoga.h>
namespace facebook::yoga {
struct YogaNodeAndConfig {
YogaNodeAndConfig(
std::shared_ptr<YGNode> node,
std::shared_ptr<const YGConfig> config,
std::vector<std::shared_ptr<YogaNodeAndConfig>> children)
: node_(std::move(node)),
config_(std::move(config)),
children_(std::move(children)) {}
std::shared_ptr<YGNode> node_;
std::shared_ptr<const YGConfig> config_;
std::vector<std::shared_ptr<YogaNodeAndConfig>> children_;
};
struct BenchmarkResult {
std::chrono::steady_clock::duration treeCreationDuration;
std::chrono::steady_clock::duration layoutDuration;
};
} // namespace facebook::yoga

View File

@@ -3,6 +3,7 @@
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13...3.26)
project(benchmark)
set(CMAKE_VERBOSE_MAKEFILE on)
@@ -12,16 +13,7 @@ include(${YOGA_ROOT}/cmake/project-defaults.cmake)
add_subdirectory(${YOGA_ROOT}/yoga ${CMAKE_CURRENT_BINARY_DIR}/yoga)
file(GLOB SOURCES_LEGACY CONFIGURE_DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/*.c)
file(GLOB SOURCES CONFIGURE_DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
${CMAKE_CURRENT_SOURCE_DIR}/*.c)
add_executable(benchmark ${SOURCES})
add_executable(benchmarklegacy ${SOURCES_LEGACY})
target_link_libraries(benchmark yogacore)
target_link_libraries(benchmarklegacy yogacore)
target_include_directories(benchmark
PRIVATE
$<BUILD_INTERFACE:${YOGA_ROOT}/lib>)

View File

@@ -1,248 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <memory>
#include <vector>
#include <benchmark/TreeDeserialization.h>
#include <capture/CaptureTree.h>
#include <nlohmann/json.hpp>
#include <yoga/Yoga.h>
namespace facebook::yoga {
using namespace nlohmann;
static inline bool isAuto(json& j) {
return j.is_string() && j == "auto";
}
static inline bool isUndefined(json& j) {
return j.is_string() && j == "undefined";
}
static inline std::string invalidArgumentMessage(
const std::string& arg,
const std::string& enumName) {
return arg + " does not represent any " + enumName + " values";
}
static inline float floatFromJson(json& j) {
float result = YGUndefined;
if (!j.is_null()) {
result = j;
}
return result;
}
YGFlexDirection flexDirectionFromString(const std::string& str) {
if (str == "row") {
return YGFlexDirectionRow;
} else if (str == "row-reverse") {
return YGFlexDirectionRowReverse;
} else if (str == "column") {
return YGFlexDirectionColumn;
} else if (str == "column-reverse") {
return YGFlexDirectionColumnReverse;
} else {
throw std::invalid_argument(invalidArgumentMessage(str, "YGFlexDirection"));
}
}
YGJustify justifyContentFromString(const std::string& str) {
if (str == "flex-start") {
return YGJustifyFlexStart;
} else if (str == "center") {
return YGJustifyCenter;
} else if (str == "flex-end") {
return YGJustifyFlexEnd;
} else if (str == "space-between") {
return YGJustifySpaceBetween;
} else if (str == "space-around") {
return YGJustifySpaceAround;
} else if (str == "space-evenly") {
return YGJustifySpaceEvenly;
} else {
throw std::invalid_argument(invalidArgumentMessage(str, "YGJustify"));
}
}
YGAlign alignFromString(const std::string& str) {
if (str == "auto") {
return YGAlignAuto;
} else if (str == "flex-start") {
return YGAlignFlexStart;
} else if (str == "center") {
return YGAlignCenter;
} else if (str == "flex-end") {
return YGAlignFlexEnd;
} else if (str == "stretch") {
return YGAlignStretch;
} else if (str == "baseline") {
return YGAlignBaseline;
} else if (str == "space-between") {
return YGAlignSpaceBetween;
} else if (str == "space-around") {
return YGAlignSpaceAround;
} else if (str == "space-evenly") {
return YGAlignSpaceEvenly;
} else {
throw std::invalid_argument(invalidArgumentMessage(str, "YGAlign"));
}
}
YGWrap wrapFromString(const std::string& str) {
if (str == "no-wrap") {
return YGWrapNoWrap;
} else if (str == "wrap") {
return YGWrapWrap;
} else if (str == "wrap-reverse") {
return YGWrapWrapReverse;
} else {
throw std::invalid_argument(invalidArgumentMessage(str, "YGAlign"));
}
}
YGOverflow overflowFromString(const std::string& str) {
if (str == "visible") {
return YGOverflowVisible;
} else if (str == "hidden") {
return YGOverflowHidden;
} else if (str == "scroll") {
return YGOverflowScroll;
} else {
throw std::invalid_argument(invalidArgumentMessage(str, "YGAlign"));
}
}
YGDisplay displayFromString(const std::string& str) {
if (str == "flex") {
return YGDisplayFlex;
} else if (str == "none") {
return YGDisplayNone;
} else {
throw std::invalid_argument(invalidArgumentMessage(str, "YGAlign"));
}
}
YGPositionType positionTypeFromString(const std::string& str) {
if (str == "static") {
return YGPositionTypeStatic;
} else if (str == "relative") {
return YGPositionTypeRelative;
} else if (str == "absolute") {
return YGPositionTypeAbsolute;
} else {
throw std::invalid_argument(invalidArgumentMessage(str, "YGAlign"));
}
}
YGUnit unitFromJson(json& j) {
if (isAuto(j)) {
return YGUnitAuto;
}
if (isUndefined(j)) {
return YGUnitUndefined;
}
std::string unit = j["unit"];
if (unit == "px") {
return YGUnitPoint;
} else if (unit == "pct") {
return YGUnitPercent;
} else {
throw std::invalid_argument(invalidArgumentMessage(unit, "YGUnit"));
}
}
YGEdge edgeFromString(const std::string& str) {
if (str == "left") {
return YGEdgeLeft;
} else if (str == "top") {
return YGEdgeTop;
} else if (str == "right") {
return YGEdgeRight;
} else if (str == "bottom") {
return YGEdgeBottom;
} else if (str == "start") {
return YGEdgeStart;
} else if (str == "end") {
return YGEdgeEnd;
} else if (str == "horizontal") {
return YGEdgeHorizontal;
} else if (str == "vertical") {
return YGEdgeVertical;
} else if (str == "all") {
return YGEdgeAll;
} else {
throw std::invalid_argument(invalidArgumentMessage(str, "YGEdge"));
}
}
YGErrata errataFromString(const std::string& str) {
if (str == "none") {
return YGErrataNone;
} else if (str == "all") {
return YGErrataAll;
} else if (str == "classic") {
return YGErrataClassic;
} else {
throw std::invalid_argument(invalidArgumentMessage(str, "YGErrata"));
}
}
YGExperimentalFeature experimentalFeatureFromString(const std::string& str) {
if (str == "web-flex-basis") {
return YGExperimentalFeatureWebFlexBasis;
} else {
throw std::invalid_argument(
invalidArgumentMessage(str, "YGExperimentalFeature"));
}
}
std::string edgeStringFromPropertyName(
const json::iterator& it,
const std::string& propertyName) {
return it.key().substr(propertyName.length() + 1);
}
YGDirection directionFromString(const std::string& str) {
if (str == "ltr") {
return YGDirectionLTR;
} else if (str == "rtl") {
return YGDirectionRTL;
} else if (str == "inherit") {
return YGDirectionInherit;
} else {
throw std::invalid_argument(invalidArgumentMessage(str, "YGDirection"));
}
}
YGMeasureMode measureModeFromString(const std::string& str) {
if (str == "at-most") {
return YGMeasureModeAtMost;
} else if (str == "exactly") {
return YGMeasureModeExactly;
} else if (str == "undefined") {
return YGMeasureModeUndefined;
} else {
throw std::invalid_argument(invalidArgumentMessage(str, "YGMeasureMode"));
}
}
SerializedMeasureFunc serializedMeasureFuncFromJson(json& j) {
return SerializedMeasureFunc{
floatFromJson(j["width"]),
measureModeFromString(j["width-mode"]),
floatFromJson(j["height"]),
measureModeFromString(j["height-mode"]),
floatFromJson(j["output-width"]),
floatFromJson(j["output-height"]),
j["duration-ns"]};
}
} // namespace facebook::yoga

View File

@@ -1,52 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <memory>
#include <vector>
#include <capture/CaptureTree.h>
#include <nlohmann/json.hpp>
#include <yoga/Yoga.h>
namespace facebook::yoga {
using namespace nlohmann;
YGFlexDirection flexDirectionFromString(const std::string& str);
YGJustify justifyContentFromString(const std::string& str);
YGAlign alignFromString(const std::string& str);
YGWrap wrapFromString(const std::string& str);
YGOverflow overflowFromString(const std::string& str);
YGDisplay displayFromString(const std::string& str);
YGPositionType positionTypeFromString(const std::string& str);
YGUnit unitFromJson(json& j);
YGEdge edgeFromString(const std::string& str);
YGErrata errataFromString(const std::string& str);
YGExperimentalFeature experimentalFeatureFromString(const std::string& str);
std::string edgeStringFromPropertyName(
const json::iterator& it,
const std::string& propertyName);
YGDirection directionFromString(const std::string& str);
YGMeasureMode measureModeFromString(const std::string& str);
SerializedMeasureFunc serializedMeasureFuncFromJson(json& j);
} // namespace facebook::yoga

View File

@@ -17,8 +17,8 @@
#define YGBENCHMARKS(BLOCK) \
int main(int argc, char const* argv[]) { \
(void)argc; \
(void)argv; \
(void) argc; \
(void) argv; \
clock_t __start; \
clock_t __endTimes[NUM_REPETITIONS]; \
{ BLOCK } \
@@ -33,8 +33,8 @@
__printBenchmarkResult(NAME, __start, __endTimes);
static int __compareDoubles(const void* a, const void* b) {
double arg1 = *(const double*)a;
double arg2 = *(const double*)b;
double arg1 = *(const double*) a;
double arg2 = *(const double*) b;
if (arg1 < arg2) {
return -1;
@@ -47,14 +47,15 @@ static int __compareDoubles(const void* a, const void* b) {
return 0;
}
static void
__printBenchmarkResult(char* name, clock_t start, clock_t* endTimes) {
static void __printBenchmarkResult(
char* name,
clock_t start,
clock_t* endTimes) {
double timesInMs[NUM_REPETITIONS];
double mean = 0;
clock_t lastEnd = start;
for (uint32_t i = 0; i < NUM_REPETITIONS; i++) {
timesInMs[i] =
((double)(endTimes[i] - lastEnd)) / (double)CLOCKS_PER_SEC * 1000;
timesInMs[i] = (endTimes[i] - lastEnd) / (double) CLOCKS_PER_SEC * 1000;
lastEnd = endTimes[i];
mean += timesInMs[i];
}
@@ -74,12 +75,12 @@ __printBenchmarkResult(char* name, clock_t start, clock_t* endTimes) {
}
static YGSize _measure(
YGNodeConstRef node,
YGNodeRef node,
float width,
YGMeasureMode widthMode,
float height,
YGMeasureMode heightMode) {
(void)node;
(void) node;
return (YGSize){
.width = widthMode == YGMeasureModeUndefined ? 10 : width,
.height = heightMode == YGMeasureModeUndefined ? 10 : height,

View File

@@ -1,15 +0,0 @@
#!/bin/sh
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cd "$(dirname "$0")" || exit
CAPTURES_PATH="$(dirname "$(realpath "$0")")""/captures"
if [ "$1" = "buck" ]; then
buck run @//fbcode/mode/opt :benchmarkCXX "${CAPTURES_PATH}"
else
cmake -B build -S . -D CMAKE_BUILD_TYPE=Release
cmake --build build
build/benchmark "${CAPTURES_PATH}"
fi

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,13 @@
/**
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
.playgroundContainer {
display: flex;
flex: 1;
plugins { `kotlin-dsl` }
repositories {
mavenCentral()
gradlePluginPortal()
}

View File

@@ -6,69 +6,18 @@
*/
plugins {
id("com.android.library")
id("maven-publish")
id("signing")
}
group = "com.facebook.yoga"
val compileSdkVersionProperty: Int by rootProject.extra
val minSdkVersionProperty: Int by rootProject.extra
val targetSdkVersionProperty: Int by rootProject.extra
val buildToolsVersionProperty: String by rootProject.extra
val ndkVersionProperty: String by rootProject.extra
android {
namespace = "com.facebook.yoga"
compileSdk = 35
buildToolsVersion = "35.0.0"
ndkVersion = "26.0.10792818"
defaultConfig {
minSdk = 21
consumerProguardFiles("proguard-rules.pro")
ndk { abiFilters.addAll(setOf("x86", "x86_64", "armeabi-v7a", "arm64-v8a")) }
}
externalNativeBuild { cmake { path("CMakeLists.txt") } }
compileOptions {
targetCompatibility(JavaVersion.VERSION_1_8)
sourceCompatibility(JavaVersion.VERSION_1_8)
}
sourceSets {
named("main") {
java.srcDir("com")
manifest.srcFile("AndroidManifest.xml")
res.srcDir("res")
}
}
publishing {
multipleVariants {
withSourcesJar()
withJavadocJar()
includeBuildTypeValues("debug", "release")
}
}
if ("USE_SNAPSHOT".byProperty.toBoolean()) {
version = "${"VERSION_NAME".byProperty}-SNAPSHOT"
} else {
version = "VERSION_NAME".byProperty.toString()
}
dependencies {
implementation("com.google.code.findbugs:jsr305:3.0.2")
implementation("com.facebook.soloader:soloader:0.10.5")
testImplementation("junit:junit:4.12")
}
version =
if ("USE_SNAPSHOT".byProperty.toBoolean()) {
"${"VERSION_NAME".byProperty}-SNAPSHOT"
} else {
"VERSION_NAME".byProperty.toString()
}
publishing {
publications {
register<MavenPublication>("default") {

View File

@@ -6,8 +6,8 @@
*/
plugins {
id("com.android.library") version "8.2.1" apply false
id("com.android.application") version "8.2.1" apply false
id("com.android.library") version "8.0.1" apply false
id("com.android.application") version "8.0.1" apply false
id("io.github.gradle-nexus.publish-plugin") version "1.3.0"
}
@@ -38,6 +38,16 @@ nexusPublishing {
}
}
ext {
buildToolsVersion = "33.0.0"
ndkVersion = "23.1.7779620"
minSdkVersion = 21
compileSdkVersion = 33
targetSdkVersion = 33
sourceCompatibilityVersion = JavaVersion.VERSION_1_8
targetCompatibilityVersion = JavaVersion.VERSION_1_8
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@@ -1,28 +0,0 @@
#!/usr/bin/env sh
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
if [ "$#" -eq 0 ]; then
build_type="Debug"
else
build_type="$1"
fi
export CC=clang
export CXX=clang++
# Make sure libc++ isn't used, as libfuzzer is linked against stdlibc++ which causes conflicts
unset CXXFLAGS
export LDFLAGS=-lstdc++
if which ninja; then
set -e
cmake -B build -S . -D BUILD_FUZZ_TESTS=ON -D CMAKE_BUILD_TYPE="$build_type" -G Ninja
else
set -e
cmake -B build -S . -D BUILD_FUZZ_TESTS=ON -D CMAKE_BUILD_TYPE="$build_type"
fi
cmake --build build --target fuzz_layout

View File

@@ -1,23 +0,0 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13...3.26)
project(capture)
set(CMAKE_VERBOSE_MAKEFILE on)
set(YOGA_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/..)
include(${YOGA_ROOT}/cmake/project-defaults.cmake)
add_subdirectory(${YOGA_ROOT}/yoga ${CMAKE_CURRENT_BINARY_DIR}/yoga)
file(GLOB SOURCES CONFIGURE_DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
add_library(capture STATIC ${SOURCES})
target_link_libraries(capture yogacore)
target_include_directories(capture
PUBLIC
$<BUILD_INTERFACE:${YOGA_ROOT}/lib>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_PREFIX}/include/yoga/lib>)

View File

@@ -1,100 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <cassert>
#include <fstream>
#include <capture/CaptureTree.h>
#include <capture/NodeToString.h>
#include <nlohmann/json.hpp>
namespace facebook::yoga {
using namespace nlohmann;
static void captureTree(
std::string_view serializedTree,
const std::filesystem::path& path) {
std::ofstream file(path);
file << serializedTree;
}
static SerializedMeasureFuncMap& currentSerializedMeasureFuncMap() {
static thread_local SerializedMeasureFuncMap map{};
return map;
}
/*
* Capturing a tree often means that we capturing multiple serial layouts over
* the course of the capture. Because of this, we need to make sure that we do
* a full layout pass with no caching. If we do not do this there is a chance
* we do not capture measure functions that were called and cached in previous
* layouts. Missing these captures would lead to inaccurate benchmarking where
* we do not have cached state.
*
* TODO: Dirty entire tree not just measure function nodes
*/
static void dirtyTree(YGNodeRef node) {
if (YGNodeHasMeasureFunc(node)) {
YGNodeMarkDirty(node);
}
const size_t childCount = YGNodeGetChildCount(node);
for (size_t i = 0; i < childCount; i++) {
dirtyTree(YGNodeGetChild(node, i));
}
}
void YGNodeCalculateLayoutWithCapture(
YGNodeRef node,
float availableWidth,
float availableHeight,
YGDirection ownerDirection,
const std::filesystem::path& path) {
dirtyTree(node);
YGNodeCalculateLayout(node, availableWidth, availableHeight, ownerDirection);
json j;
serializeLayoutInputs(j, availableWidth, availableHeight, ownerDirection);
serializeTree(
j,
currentSerializedMeasureFuncMap(),
node,
PrintOptions::Style | PrintOptions::Children | PrintOptions::Config |
PrintOptions::Node);
// TODO: It is possible to have a measure function call layout again if, e.g.,
// views are nested in text. Need to be able to resolve this special case.
currentSerializedMeasureFuncMap().clear();
captureTree(j.dump(2), path);
}
void captureMeasureFunc(
YGNodeConstRef node,
float width,
YGMeasureMode widthMode,
float height,
YGMeasureMode heightMode,
YGSize output,
std::chrono::steady_clock::duration durationNs) {
auto measureFuncIt = currentSerializedMeasureFuncMap().find(node);
if (measureFuncIt == currentSerializedMeasureFuncMap().end()) {
std::vector<SerializedMeasureFunc> vec{};
currentSerializedMeasureFuncMap().insert(std::make_pair(node, vec));
}
measureFuncIt = currentSerializedMeasureFuncMap().find(node);
assert(measureFuncIt != currentSerializedMeasureFuncMap().end());
measureFuncIt->second.push_back(
{width,
widthMode,
height,
heightMode,
output.width,
output.height,
durationNs.count()});
}
} // namespace facebook::yoga

View File

@@ -1,47 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <filesystem>
#include <unordered_map>
#include <vector>
#include <yoga/Yoga.h>
namespace facebook::yoga {
struct SerializedMeasureFunc {
float inputWidth{0.0f};
YGMeasureMode widthMode{YGMeasureModeUndefined};
float inputHeight{0.0};
YGMeasureMode heightMode{YGMeasureModeUndefined};
float outputWidth{0.0f};
float outputHeight{0.0f};
std::chrono::steady_clock::duration::rep durationNs{};
};
using SerializedMeasureFuncMap =
std::unordered_map<YGNodeConstRef, std::vector<SerializedMeasureFunc>>;
void YGNodeCalculateLayoutWithCapture(
YGNodeRef node,
float availableWidth,
float availableHeight,
YGDirection ownerDirection,
const std::filesystem::path& path);
void captureMeasureFunc(
YGNodeConstRef node,
float width,
YGMeasureMode widthMode,
float height,
YGMeasureMode heightMode,
YGSize output,
std::chrono::steady_clock::duration durationNs);
} // namespace facebook::yoga

View File

@@ -1,359 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <memory>
#include <vector>
#include <capture/NodeToString.h>
namespace facebook::yoga {
using namespace nlohmann;
static void appendFloatIfNotDefault(
json& j,
std::string_view key,
float num,
float defaultNum) {
if (num != defaultNum && !YGFloatIsUndefined(num)) {
j[key] = num;
}
}
static void appendYGValueIfNotDefault(
json& j,
std::string_view key,
const YGValue& value,
const YGValue& defaultValue) {
if (value != defaultValue) {
if (value.unit == YGUnitAuto) {
j[key] = "auto";
} else if (value.unit == YGUnitUndefined) {
j[key] = "undefined";
} else {
std::string unit = value.unit == YGUnitPoint ? "px" : "pct";
j[key]["value"] = value.value;
j[key]["unit"] = unit;
}
}
}
static void appendEnumValueIfNotDefault(
json& j,
std::string_view key,
std::string_view value,
std::string_view defaultValue) {
if (value != defaultValue) {
j[key] = value;
}
}
static void appendBoolIfNotDefault(
json& j,
std::string_view key,
bool value,
bool defaultValue) {
if (value != defaultValue) {
j[key] = value;
}
}
template <auto Field>
static void appendEdges(
json& j,
const std::string& key,
YGNodeRef node,
YGNodeRef defaultNode) {
appendYGValueIfNotDefault(
j["style"],
key + "-left",
(*Field)(node, YGEdgeLeft),
(*Field)(defaultNode, YGEdgeLeft));
appendYGValueIfNotDefault(
j["style"],
key + "-right",
(*Field)(node, YGEdgeRight),
(*Field)(defaultNode, YGEdgeRight));
appendYGValueIfNotDefault(
j["style"],
key + "-top",
(*Field)(node, YGEdgeTop),
(*Field)(defaultNode, YGEdgeTop));
appendYGValueIfNotDefault(
j["style"],
key + "-bottom",
(*Field)(node, YGEdgeBottom),
(*Field)(defaultNode, YGEdgeBottom));
appendYGValueIfNotDefault(
j["style"],
key + "-all",
(*Field)(node, YGEdgeAll),
(*Field)(defaultNode, YGEdgeAll));
appendYGValueIfNotDefault(
j["style"],
key + "-start",
(*Field)(node, YGEdgeStart),
(*Field)(defaultNode, YGEdgeStart));
appendYGValueIfNotDefault(
j["style"],
key + "-end",
(*Field)(node, YGEdgeEnd),
(*Field)(defaultNode, YGEdgeEnd));
appendYGValueIfNotDefault(
j["style"],
key + "-vertical",
(*Field)(node, YGEdgeVertical),
(*Field)(defaultNode, YGEdgeVertical));
appendYGValueIfNotDefault(
j["style"],
key + "-horizontal",
(*Field)(node, YGEdgeHorizontal),
(*Field)(defaultNode, YGEdgeHorizontal));
}
static YGValue borderFloatToYGValue(YGNodeRef node, YGEdge edge) {
float val = YGNodeStyleGetBorder(node, edge);
YGUnit unit = YGFloatIsUndefined(val) ? YGUnitUndefined : YGUnitPoint;
return YGValue{val, unit};
}
static void serializeMeasureFuncResults(
json& j,
std::vector<SerializedMeasureFunc>& measureFuncs) {
for (auto measureFunc : measureFuncs) {
j["measure-funcs"].push_back(
{{"width", measureFunc.inputWidth},
{"width-mode", YGMeasureModeToString(measureFunc.widthMode)},
{"height", measureFunc.inputHeight},
{"height-mode", YGMeasureModeToString(measureFunc.heightMode)},
{"output-width", measureFunc.outputWidth},
{"output-height", measureFunc.outputHeight},
{"duration-ns", measureFunc.durationNs}});
}
}
static void serializeTreeImpl(
json& j,
SerializedMeasureFuncMap& nodesToMeasureFuncs,
YGNodeRef node,
PrintOptions options) {
if ((options & PrintOptions::Layout) == PrintOptions::Layout) {
j["layout"]["width"] = YGNodeStyleGetWidth(node).value;
j["layout"]["height"] = YGNodeStyleGetHeight(node).value;
j["layout"]["top"] = YGNodeStyleGetPosition(node, YGEdgeTop).value;
j["layout"]["left"] = YGNodeStyleGetPosition(node, YGEdgeLeft).value;
}
std::unique_ptr<YGNode, decltype(&YGNodeFree)> defaultNode(
YGNodeNew(), YGNodeFree);
if ((options & PrintOptions::Style) == PrintOptions::Style) {
appendEnumValueIfNotDefault(
j["style"],
"flex-direction",
YGFlexDirectionToString(YGNodeStyleGetFlexDirection(node)),
YGFlexDirectionToString(
YGNodeStyleGetFlexDirection(defaultNode.get())));
appendEnumValueIfNotDefault(
j["style"],
"justify-content",
YGJustifyToString(YGNodeStyleGetJustifyContent(node)),
YGJustifyToString(YGNodeStyleGetJustifyContent(defaultNode.get())));
appendEnumValueIfNotDefault(
j["style"],
"align-items",
YGAlignToString(YGNodeStyleGetAlignItems(node)),
YGAlignToString(YGNodeStyleGetAlignItems(defaultNode.get())));
appendEnumValueIfNotDefault(
j["style"],
"align-content",
YGAlignToString(YGNodeStyleGetAlignContent(node)),
YGAlignToString(YGNodeStyleGetAlignContent(defaultNode.get())));
appendEnumValueIfNotDefault(
j["style"],
"align-self",
YGAlignToString(YGNodeStyleGetAlignSelf(node)),
YGAlignToString(YGNodeStyleGetAlignSelf(defaultNode.get())));
appendEnumValueIfNotDefault(
j["style"],
"flex-wrap",
YGWrapToString(YGNodeStyleGetFlexWrap(node)),
YGWrapToString(YGNodeStyleGetFlexWrap(defaultNode.get())));
appendEnumValueIfNotDefault(
j["style"],
"overflow",
YGOverflowToString(YGNodeStyleGetOverflow(node)),
YGOverflowToString(YGNodeStyleGetOverflow(defaultNode.get())));
appendEnumValueIfNotDefault(
j["style"],
"display",
YGDisplayToString(YGNodeStyleGetDisplay(node)),
YGDisplayToString(YGNodeStyleGetDisplay(defaultNode.get())));
appendEnumValueIfNotDefault(
j["style"],
"position-type",
YGPositionTypeToString(YGNodeStyleGetPositionType(node)),
YGPositionTypeToString(YGNodeStyleGetPositionType(defaultNode.get())));
appendFloatIfNotDefault(
j["style"],
"flex-grow",
YGNodeStyleGetFlexGrow(node),
YGNodeStyleGetFlexGrow(defaultNode.get()));
appendFloatIfNotDefault(
j["style"],
"flex-shrink",
YGNodeStyleGetFlexShrink(node),
YGNodeStyleGetFlexShrink(defaultNode.get()));
appendFloatIfNotDefault(
j["style"],
"flex",
YGNodeStyleGetFlex(node),
YGNodeStyleGetFlex(defaultNode.get()));
appendYGValueIfNotDefault(
j["style"],
"flex-basis",
YGNodeStyleGetFlexBasis(node),
YGNodeStyleGetFlexBasis(defaultNode.get()));
appendEdges<&YGNodeStyleGetMargin>(j, "margin", node, defaultNode.get());
appendEdges<&YGNodeStyleGetPadding>(j, "padding", node, defaultNode.get());
appendEdges<&borderFloatToYGValue>(j, "border", node, defaultNode.get());
appendEdges<&YGNodeStyleGetPosition>(
j, "position", node, defaultNode.get());
appendFloatIfNotDefault(
j["style"],
"gap",
YGNodeStyleGetGap(node, YGGutterAll),
YGNodeStyleGetGap(defaultNode.get(), YGGutterAll));
appendFloatIfNotDefault(
j["style"],
"column-gap",
YGNodeStyleGetGap(node, YGGutterColumn),
YGNodeStyleGetGap(defaultNode.get(), YGGutterColumn));
appendFloatIfNotDefault(
j["style"],
"row-gap",
YGNodeStyleGetGap(node, YGGutterRow),
YGNodeStyleGetGap(defaultNode.get(), YGGutterRow));
appendYGValueIfNotDefault(
j["style"],
"width",
YGNodeStyleGetWidth(node),
YGNodeStyleGetWidth(defaultNode.get()));
appendYGValueIfNotDefault(
j["style"],
"height",
YGNodeStyleGetHeight(node),
YGNodeStyleGetHeight(defaultNode.get()));
appendYGValueIfNotDefault(
j["style"],
"max-width",
YGNodeStyleGetMaxWidth(node),
YGNodeStyleGetMaxWidth(defaultNode.get()));
appendYGValueIfNotDefault(
j["style"],
"max-height",
YGNodeStyleGetMaxHeight(node),
YGNodeStyleGetMaxHeight(defaultNode.get()));
appendYGValueIfNotDefault(
j["style"],
"min-width",
YGNodeStyleGetMinWidth(node),
YGNodeStyleGetMinWidth(defaultNode.get()));
appendYGValueIfNotDefault(
j["style"],
"min-height",
YGNodeStyleGetMinHeight(node),
YGNodeStyleGetMinHeight(defaultNode.get()));
}
if ((options & PrintOptions::Config) == PrintOptions::Config) {
YGConfigConstRef config = YGNodeGetConfig(node);
YGConfigConstRef defaultConfig = YGConfigGetDefault();
appendBoolIfNotDefault(
j["config"],
"use-web-defaults",
YGConfigGetUseWebDefaults(config),
YGConfigGetUseWebDefaults(defaultConfig));
appendFloatIfNotDefault(
j["config"],
"point-scale-factor",
YGConfigGetPointScaleFactor(config),
YGConfigGetPointScaleFactor(defaultConfig));
YGErrata errata = YGConfigGetErrata(config);
if (errata == YGErrataNone || errata == YGErrataAll ||
errata == YGErrataClassic) {
appendEnumValueIfNotDefault(
j["config"],
"errata",
YGErrataToString(errata),
YGErrataToString(YGConfigGetErrata(defaultConfig)));
}
if (YGConfigIsExperimentalFeatureEnabled(
config, YGExperimentalFeatureWebFlexBasis) !=
YGConfigIsExperimentalFeatureEnabled(
defaultConfig, YGExperimentalFeatureWebFlexBasis)) {
j["config"]["experimental-features"].push_back(
YGExperimentalFeatureToString(YGExperimentalFeatureWebFlexBasis));
}
}
if ((options & PrintOptions::Node) == PrintOptions::Node) {
appendBoolIfNotDefault(
j["node"],
"always-forms-containing-block",
YGNodeGetAlwaysFormsContainingBlock(node),
YGNodeGetAlwaysFormsContainingBlock(defaultNode.get()));
if (YGNodeHasMeasureFunc(node)) {
auto measureFuncIt = nodesToMeasureFuncs.find(node);
if (measureFuncIt == nodesToMeasureFuncs.end()) {
j["node"]["measure-funcs"];
} else {
serializeMeasureFuncResults(j["node"], measureFuncIt->second);
}
}
}
const size_t childCount = YGNodeGetChildCount(node);
if ((options & PrintOptions::Children) == PrintOptions::Children &&
childCount > 0) {
for (size_t i = 0; i < childCount; i++) {
j["children"].push_back({});
serializeTreeImpl(
j["children"][i],
nodesToMeasureFuncs,
YGNodeGetChild(node, i),
options);
}
}
}
void serializeTree(
json& j,
SerializedMeasureFuncMap& nodesToMeasureFuncs,
YGNodeRef node,
PrintOptions options) {
serializeTreeImpl(j["tree"], nodesToMeasureFuncs, node, options);
}
void serializeLayoutInputs(
json& j,
float availableWidth,
float availableHeight,
YGDirection ownerDirection) {
j["layout-inputs"] = {
{"available-width", availableWidth},
{"available-height", availableHeight},
{"owner-direction", YGDirectionToString(ownerDirection)},
};
}
} // namespace facebook::yoga

View File

@@ -1,40 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <chrono>
#include <string>
#include <capture/CaptureTree.h>
#include <nlohmann/json.hpp>
#include <yoga/Yoga.h>
namespace facebook::yoga {
enum class PrintOptions : uint8_t {
Layout = 1 << 0,
Children = 1 << 1,
Style = 1 << 2,
Config = 1 << 3,
Node = 1 << 4,
};
YG_DEFINE_ENUM_FLAG_OPERATORS(PrintOptions);
void serializeTree(
nlohmann::json& j,
SerializedMeasureFuncMap& nodesToMeasureFuncs,
YGNodeRef node,
PrintOptions options);
void serializeLayoutInputs(
nlohmann::json& j,
float availableWidth,
float availableHeight,
YGDirection ownerDirection);
} // namespace facebook::yoga

View File

@@ -3,7 +3,7 @@
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
@@ -17,7 +17,7 @@ add_compile_options(
# "Standard C++ exception handling" (C++ stack unwinding including extern c)
/EHsc
# Enable warnings and warnings as errors
/W4
/W3
/WX
# Disable RTTI
$<$<COMPILE_LANGUAGE:CXX>:/GR->
@@ -33,6 +33,7 @@ add_compile_options(
-fexceptions
# Enable warnings and warnings as errors
-Wall
-Wextra
-Werror
# Disable RTTI
$<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>

View File

@@ -4,7 +4,6 @@
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
import math
import os
ENUMS = {
@@ -29,7 +28,6 @@ ENUMS = {
"Baseline",
"SpaceBetween",
"SpaceAround",
"SpaceEvenly",
],
"PositionType": ["Static", "Relative", "Absolute"],
"Display": ["Flex", "None"],
@@ -52,6 +50,15 @@ ENUMS = {
"ExperimentalFeature": [
# Mimic web flex-basis behavior (experiment may be broken)
"WebFlexBasis",
# Conformance fix: https://github.com/facebook/yoga/pull/1028
"AbsolutePercentageAgainstPaddingEdge",
# fix JNI local ref overflows
"FixJNILocalRefOverflows",
],
"PrintOptions": [
("Layout", 1 << 0),
("Style", 1 << 1),
("Children", 1 << 2),
],
"Gutter": ["Column", "Row", "All"],
# Known incorrect behavior which can be enabled for compatibility
@@ -61,12 +68,6 @@ ENUMS = {
# Allows main-axis flex basis to be stretched without flexGrow being
# set (previously referred to as "UseLegacyStretchBehaviour")
("StretchFlexBasis", 1 << 0),
# Positioning of absolute nodes will have various bugs related to
# justification, alignment, and insets
("AbsolutePositioningIncorrect", 1 << 1),
# Absolute nodes will resolve percentages against the inner size of
# their containing node, not the padding box
("AbsolutePercentAgainstInnerSize", 1 << 2),
# Enable all incorrect behavior (preserve compatibility)
("All", 0x7FFFFFFF),
# Enable all errata except for "StretchFlexBasis" (Defaults behavior
@@ -77,7 +78,7 @@ ENUMS = {
DO_NOT_STRIP = ["LogLevel"]
BITSET_ENUMS = ["Errata"]
BITSET_ENUMS = ["PrintOptions", "Errata"]
def get_license(ext):
@@ -89,7 +90,7 @@ def get_license(ext):
*/
// @{"generated"} by enums.py
{"// clang-format off" if ext == "cpp" else ""}
"""
@@ -122,11 +123,15 @@ with open(root + "/yoga/YGEnums.h", "w") as f:
f.write(get_license("cpp"))
f.write("#pragma once\n")
f.write("#include <yoga/YGMacros.h>\n\n")
f.write("// clang-format off\n\n\n")
f.write("YG_EXTERN_C_BEGIN\n\n")
items = sorted(ENUMS.items())
for name, values in items:
f.write("YG_ENUM_DECL(\n")
if isinstance(values[0], tuple):
f.write("YG_ENUM_DECL(\n")
else:
f.write("YG_ENUM_SEQ_DECL(\n")
f.write(" YG%s,\n" % name)
for value in values:
@@ -143,47 +148,6 @@ with open(root + "/yoga/YGEnums.h", "w") as f:
f.write("\n")
f.write("YG_EXTERN_C_END\n")
# Write out C++ scoped enums
for name, values in sorted(ENUMS.items()):
with open(f"{root}/yoga/enums/{name}.h", "w") as f:
f.write(get_license("cpp"))
f.write("#pragma once\n\n")
f.write("#include <cstdint>\n")
f.write("#include <yoga/YGEnums.h>\n")
f.write("#include <yoga/enums/YogaEnums.h>\n\n")
f.write("namespace facebook::yoga {\n\n")
width = "uint32_t" if name in BITSET_ENUMS else "uint8_t"
f.write(f"enum class {name} : {width} {{\n")
for value in values:
ordinal = value[0] if isinstance(value, tuple) else value
f.write(f" {ordinal} = YG{name}{ordinal},\n")
f.write("};\n\n")
if name in BITSET_ENUMS:
f.write(f"YG_DEFINE_ENUM_FLAG_OPERATORS({name})\n\n")
else:
f.write("template <>\n")
f.write(f"constexpr int32_t ordinalCount<{name}>() {{\n")
f.write(f" return {len(values)};\n")
f.write("}\n\n")
f.write(f"constexpr {name} scopedEnum(YG{name} unscoped) {{\n")
f.write(f" return static_cast<{name}>(unscoped);\n")
f.write("}\n\n")
f.write(f"constexpr YG{name} unscopedEnum({name} scoped) {{\n")
f.write(f" return static_cast<YG{name}>(scoped);\n")
f.write("}\n\n")
f.write(f"inline const char* toString({name} e) {{\n")
f.write(f" return YG{name}ToString(unscopedEnum(e));\n")
f.write("}\n\n")
f.write("} // namespace facebook::yoga\n")
# write out C body for printing
with open(root + "/yoga/YGEnums.cpp", "w") as f:
f.write(get_license("cpp"))

View File

@@ -1,21 +0,0 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
# If google/oss-fuzz has set the fuzzing engine
if(DEFINED ENV{LIB_FUZZING_ENGINE})
set(FUZZING_ENGINE $ENV{LIB_FUZZING_ENGINE})
set(FUZZING_COMPILE_FLAGS "")
set(FUZZING_LINK_FLAGS "${FUZZING_ENGINE}")
else()
set(FUZZING_COMPILE_FLAGS "-fsanitize=fuzzer")
set(FUZZING_LINK_FLAGS "-fsanitize=fuzzer")
endif()
add_executable(fuzz_layout FuzzLayout.cpp)
set_target_properties(fuzz_layout PROPERTIES
COMPILE_FLAGS "${FUZZING_COMPILE_FLAGS}"
LINK_FLAGS "${FUZZING_LINK_FLAGS}"
)
target_link_libraries(fuzz_layout yogacore)

View File

@@ -1,57 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <fuzzer/FuzzedDataProvider.h>
#include <yoga/Yoga.h>
#include <cstdint>
YGFlexDirection fuzzedFlexDirection(FuzzedDataProvider& fdp) {
return fdp.PickValueInArray({
YGFlexDirectionColumn,
YGFlexDirectionColumnReverse,
YGFlexDirectionRow,
YGFlexDirectionRowReverse,
});
}
void fillFuzzedTree(
FuzzedDataProvider& fdp,
YGConfigConstRef config,
YGNodeRef root,
size_t depth = 0) {
constexpr size_t kMaxDepth = 20;
constexpr size_t kMaxChildren = 20;
if (depth > kMaxDepth) {
return;
}
size_t children = fdp.ConsumeIntegralInRange<size_t>(0, kMaxChildren);
for (size_t i = 0; i < children; i++) {
YGNodeRef child = YGNodeNewWithConfig(config);
YGNodeStyleSetFlexDirection(root, fuzzedFlexDirection(fdp));
YGNodeStyleSetWidth(child, fdp.ConsumeFloatingPoint<float>());
YGNodeStyleSetGap(
child, YGGutterAll, fdp.ConsumeProbability<float>() * 100);
YGNodeStyleSetHeight(child, fdp.ConsumeFloatingPoint<float>());
YGNodeInsertChild(root, child, i);
fillFuzzedTree(fdp, config, child, depth + 1);
}
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
FuzzedDataProvider fdp(data, size);
YGConfigRef config = YGConfigNew();
YGNodeRef root = YGNodeNewWithConfig(config);
fillFuzzedTree(fdp, config, root);
YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR);
YGNodeFreeRecursive(root);
YGConfigFree(config);
return 0;
}

4
gentest/Gemfile Normal file
View File

@@ -0,0 +1,4 @@
source "https://rubygems.org"
gem 'watir', '~>7.2.0'
gem 'webdrivers', '~> 5.2.0'

31
gentest/Gemfile.lock Normal file
View File

@@ -0,0 +1,31 @@
GEM
remote: https://rubygems.org/
specs:
nokogiri (1.15.2-arm64-darwin)
racc (~> 1.4)
racc (1.7.0)
regexp_parser (2.8.1)
rexml (3.2.5)
rubyzip (2.3.2)
selenium-webdriver (4.10.0)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
watir (7.2.2)
regexp_parser (>= 1.2, < 3)
selenium-webdriver (~> 4.2)
webdrivers (5.2.0)
nokogiri (~> 1.6)
rubyzip (>= 1.3.0)
selenium-webdriver (~> 4.0)
websocket (1.2.9)
PLATFORMS
arm64-darwin-22
DEPENDENCIES
watir (~> 7.2.0)
webdrivers (~> 5.2.0)
BUNDLED WITH
2.4.10

View File

@@ -2,18 +2,6 @@
<div style="width:10px; height: 10px; position: absolute; start: 10px; top: 10px;"></div>
</div>
<div id="absolute_layout_width_height_left_auto_right" style="width: 100px; height: 100px">
<div style="width: 10px; height: 10px; position: absolute; left: auto; right: 10px;"></div>
</div>
<div id="absolute_layout_width_height_left_right_auto" style="width: 100px; height: 100px">
<div style="width: 10px; height: 10px; position: absolute; left: 10px; right: auto;"></div>
</div>
<div id="absolute_layout_width_height_left_auto_right_auto" style="width: 100px; height: 100px">
<div style="width: 10px; height: 10px; position: absolute; left: auto; right: auto;"></div>
</div>
<div id="absolute_layout_width_height_end_bottom" style="width: 100px; height: 100px;">
<div style="width:10px; height: 10px; position: absolute; end: 10px; bottom: 10px;"></div>
</div>
@@ -26,33 +14,28 @@
<div style="width:10px; height: 10px; position: absolute; start: 10px; top: 10px; end: 10px; bottom: 10px;"></div>
</div>
<div id="do_not_clamp_height_of_absolute_node_to_height_of_its_overflow_hidden_parent"
style="height: 50px; width: 50px; overflow: hidden; flex-direction: row;">
<div id="do_not_clamp_height_of_absolute_node_to_height_of_its_overflow_hidden_parent" style="height: 50px; width: 50px; overflow: hidden; flex-direction: row;">
<div style="position: absolute; start: 0px; top: 0px;">
<div style="width: 100px; height: 100px;"></div>
</div>
</div>
<div id="absolute_layout_within_border"
style="height:100px; width:100px; border-width: 10px; margin: 10px; padding: 10px;">
<div id="absolute_layout_within_border" style="height:100px; width:100px; border-width: 10px; margin: 10px; padding: 10px;">
<div style="position: absolute; width: 50px; height: 50px; left: 0px; top: 0px;"></div>
<div style="position: absolute; width: 50px; height: 50px; right: 0px; bottom: 0px;"></div>
<div style="position: absolute; width: 50px; height: 50px; left: 0px; top: 0px; margin: 10px;"></div>
<div style="position: absolute; width: 50px; height: 50px; right: 0px; bottom: 0px; margin: 10px;"></div>
</div>
<div id="absolute_layout_align_items_and_justify_content_center"
style="height: 100px; width: 110px; flex-grow: 1; align-items: center; justify-content: center;">
<div id="absolute_layout_align_items_and_justify_content_center" style="height: 100px; width: 110px; flex-grow: 1; align-items: center; justify-content: center;">
<div style="position: absolute; width: 60px; height: 40px;"></div>
</div>
<div id="absolute_layout_align_items_and_justify_content_flex_end"
style="height: 100px; width: 110px; flex-grow: 1; align-items: flex-end; justify-content: flex-end;">
<div id="absolute_layout_align_items_and_justify_content_flex_end" style="height: 100px; width: 110px; flex-grow: 1; align-items: flex-end; justify-content: flex-end;">
<div style="position: absolute; width: 60px; height: 40px;"></div>
</div>
<div id="absolute_layout_justify_content_center"
style="height: 100px; width: 110px; flex-grow: 1; justify-content: center;">
<div id="absolute_layout_justify_content_center" style="height: 100px; width: 110px; flex-grow: 1; justify-content: center;">
<div style="position: absolute; width: 60px; height: 40px;"></div>
</div>
@@ -64,23 +47,19 @@
<div style="position: absolute; width: 60px; height: 40px;align-self: center;"></div>
</div>
<div id="absolute_layout_align_items_and_justify_content_center_and_top_position"
style="height: 100px; width: 110px; flex-grow: 1; align-items: center; justify-content: center;">
<div id="absolute_layout_align_items_and_justify_content_center_and_top_position" style="height: 100px; width: 110px; flex-grow: 1; align-items: center; justify-content: center;">
<div style="position: absolute; width: 60px; height: 40px;top:10px;"></div>
</div>
<div id="absolute_layout_align_items_and_justify_content_center_and_bottom_position"
style="height: 100px; width: 110px; flex-grow: 1; align-items: center; justify-content: center;">
<div id="absolute_layout_align_items_and_justify_content_center_and_bottom_position" style="height: 100px; width: 110px; flex-grow: 1; align-items: center; justify-content: center;">
<div style="position: absolute; width: 60px; height: 40px;bottom:10px;"></div>
</div>
<div id="absolute_layout_align_items_and_justify_content_center_and_left_position"
style="height: 100px; width: 110px; flex-grow: 1; align-items: center; justify-content: center;">
<div id="absolute_layout_align_items_and_justify_content_center_and_left_position" style="height: 100px; width: 110px; flex-grow: 1; align-items: center; justify-content: center;">
<div style="position: absolute; width: 60px; height: 40px;left:5px;"></div>
</div>
<div id="absolute_layout_align_items_and_justify_content_center_and_right_position"
style="height: 100px; width: 110px; flex-grow: 1; align-items: center; justify-content: center;">
<div id="absolute_layout_align_items_and_justify_content_center_and_right_position" style="height: 100px; width: 110px; flex-grow: 1; align-items: center; justify-content: center;">
<div style="position: absolute; width: 60px; height: 40px;right:5px;"></div>
</div>
@@ -88,28 +67,24 @@
</div>
<div id="absolute_layout_percentage_bottom_based_on_parent_height" style="width: 100px; height: 200px;">
<div style="position: absolute; top: 50%; width: 10px; height: 10px;"></div>
<div style="position: absolute; bottom: 50%; width: 10px; height: 10px;"></div>
<div style="position: absolute; top: 10%; width: 10px; bottom: 10%;"></div>
<div style="position: absolute; top: 50%; width: 10px; height: 10px;"></div>
<div style="position: absolute; bottom: 50%; width: 10px; height: 10px;"></div>
<div style="position: absolute; top: 10%; width: 10px; bottom: 10%;"></div>
</div>
<div id="absolute_layout_in_wrap_reverse_column_container"
style="flex-direction:column; width:100px; height:100px; flex-wrap: wrap-reverse;">
<div id="absolute_layout_in_wrap_reverse_column_container" style="flex-direction:column; width:100px; height:100px; flex-wrap: wrap-reverse;">
<div style="width:20px; height:20px; position: absolute;"></div>
</div>
<div id="absolute_layout_in_wrap_reverse_row_container"
style="flex-direction:row; width:100px; height:100px; flex-wrap: wrap-reverse;">
<div id="absolute_layout_in_wrap_reverse_row_container" style="flex-direction:row; width:100px; height:100px; flex-wrap: wrap-reverse;">
<div style="width:20px; height:20px; position: absolute;"></div>
</div>
<div id="absolute_layout_in_wrap_reverse_column_container_flex_end"
style="flex-direction:column; width:100px; height:100px; flex-wrap: wrap-reverse;">
<div id="absolute_layout_in_wrap_reverse_column_container_flex_end" style="flex-direction:column; width:100px; height:100px; flex-wrap: wrap-reverse;">
<div style="width:20px; height:20px; position: absolute; align-self: flex-end;"></div>
</div>
<div id="absolute_layout_in_wrap_reverse_row_container_flex_end"
style="flex-direction:row; width:100px; height:100px; flex-wrap: wrap-reverse;">
<div id="absolute_layout_in_wrap_reverse_row_container_flex_end" style="flex-direction:row; width:100px; height:100px; flex-wrap: wrap-reverse;">
<div style="width:20px; height:20px; position: absolute; align-self: flex-end;"></div>
</div>
@@ -118,51 +93,10 @@
<div style="width:20%; height:20%; left:20%; top:20%; position: absolute;"></div>
</div>
<div id="absolute_layout_percentage_height_based_on_padded_parent"
style="flex-direction:column; width:100px; height:100px; padding-top: 10px; border-top: 10px solid black;">
<div id="absolute_layout_percentage_height_based_on_padded_parent" style="flex-direction:column; width:100px; height:100px; padding-top: 10px; border-top: 10px;">
<div style="width:100px; height:50%; position: absolute;"></div>
</div>
<div id="absolute_layout_percentage_height_based_on_padded_parent_and_align_items_center"
style="position: relative; flex-direction:column; align-items:center; justify-content:center; width:100px; height:100px; padding-top:20px; padding-bottom:20px; ">
<div style="position:absolute; width:100px; height:50%;"></div>
</div>
<div id="absolute_layout_padding_left" style="width:200px; height:200px; padding-left:100px;">
<div style="position:absolute; width:50px; height:50px;"></div>
</div>
<div id="absolute_layout_padding_right" style="width:200px; height:200px; padding-right:100px;">
<div style="position:absolute; width:50px; height:50px;"></div>
</div>
<div id="absolute_layout_padding_top" style="width:200px; height:200px; padding-top:100px;">
<div style="position:absolute; width:50px; height:50px;"></div>
</div>
<div id="absolute_layout_padding_bottom" style="width:200px; height:200px; padding-bottom:100px;">
<div style="position:absolute; width:50px; height:50px;"></div>
</div>
<div id="absolute_layout_padding">
<div style="width:200px; height:200px; margin:10px; position: relative">
<div style="position:static; width:200px; height:200px; padding: 50px;">
<div style="position:absolute; width:50px; height:50px;"></div>
</div>
</div>
</div>
<div id="absolute_layout_border">
<div style="width:200px; height:200px; margin:10px; position: relative">
<div style="position:static; width:200px; height:200px; border: 10px solid black;">
<div style="position:absolute; width:50px; height:50px;"></div>
</div>
</div>
</div>
<div id="absolute_layout_column_reverse_margin_border"
style="width:200px; height:200px; flex-direction: column-reverse;">
<div
style="position:absolute; width:50px; height:50px; left: 5px; right: 3px; margin-right: 4px; margin-left: 3px; border-right: 7px solid black; border-left: 1px solid black">
</div>
<div id="absolute_layout_percentage_height_based_on_padded_parent_and_align_items_center" style="position: relative; flex-direction:column; align-items:center; justify-content:center; width:100px; height:100px; padding-top:20px; padding-bottom:20px; ">
<div style="position:absolute; width:100px; height:50%;"></div>
</div>

View File

@@ -1,11 +1,4 @@
<!-- ALIGN CONTENT: FLEX-START -->
<div id="align_content_flex_start_nowrap" style="width: 140px; height: 120px; flex-direction: row; align-content: flex-start;">
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
</div>
<div id="align_content_flex_start_wrap" style="width: 140px; height: 120px; flex-wrap: wrap; flex-direction: row; align-content: flex-start;">
<div id="align_content_flex_start" style="width: 130px; height: 100px; flex-wrap: wrap; flex-direction: row; align-content: flex-start;">
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
@@ -13,28 +6,6 @@
<div style="width: 50px; height: 10px;"></div>
</div>
<!-- This tests the case where `flex-wrap: wrap` but the content does not actually wrap -->
<div id="align_content_flex_start_wrap_singleline" style="width: 140px; height: 120px; flex-wrap: wrap; flex-direction: row; align-content: flex-start;">
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
</div>
<div id="align_content_flex_start_wrapped_negative_space" style="display: flex; flex-direction: column; width: 320px; height: 320px; border-width: 60px;">
<div style="display: flex; flex-direction: row; flex-wrap: wrap; align-content: flex-start; justify-content: center; height: 10px;">
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
</div>
</div>
<div id="align_content_flex_start_wrapped_negative_space_gap" style="display: flex; flex-direction: column; width: 320px; height: 320px; border-width: 60px;">
<div style="display: flex; flex-direction: row; flex-wrap: wrap; align-content: flex-start; justify-content: center; height: 10px; gap: 10px;">
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
</div>
</div>
<div id="align_content_flex_start_without_height_on_children" style="width: 100px; height: 100px; flex-wrap: wrap; flex-direction: column; align-content: flex-start;">
<div style="width: 50px;"></div>
<div style="width: 50px; height: 10px;"></div>
@@ -51,14 +22,7 @@
<div style="width: 50px;"></div>
</div>
<!-- ALIGN CONTENT: FLEX-END -->
<div id="align_content_flex_end_nowrap" style="width: 140px; height: 120px; flex-direction: row; align-content: flex-end;">
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
</div>
<div id="align_content_flex_end_wrap" style="width: 140px; height: 120px; flex-wrap: wrap; flex-direction: row; align-content: flex-end;">
<div id="align_content_flex_end" style="width: 100px; height: 100px; flex-wrap: wrap; flex-direction: column; align-content: flex-end;">
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
@@ -66,197 +30,6 @@
<div style="width: 50px; height: 10px;"></div>
</div>
<!-- This tests the case where `flex-wrap: wrap` but the content does not actually wrap -->
<div id="align_content_flex_end_wrap_singleline" style="width: 140px; height: 120px; flex-wrap: wrap; flex-direction: row; align-content: flex-end;">
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
</div>
<div id="align_content_flex_end_wrapped_negative_space" style="display: flex; flex-direction: column; width: 320px; height: 320px; border-width: 60px;">
<div style="display: flex; flex-direction: row; flex-wrap: wrap; align-content: flex-end; justify-content: center; height: 10px;">
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
</div>
</div>
<div id="align_content_flex_end_wrapped_negative_space_gap" style="display: flex; flex-direction: column; width: 320px; height: 320px; border-width: 60px;">
<div style="display: flex; flex-direction: row; flex-wrap: wrap; align-content: flex-end; justify-content: center; height: 10px; gap: 10px;">
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
</div>
</div>
<!-- ALIGN CONTENT: CENTER -->
<div id="align_content_center_nowrap" style="width: 140px; height: 120px; flex-direction: row; align-content: center;">
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
</div>
<div id="align_content_center_wrap" style="width: 140px; height: 120px; flex-wrap: wrap; flex-direction: row; align-content: center;">
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
</div>
<!-- This tests the case where `flex-wrap: wrap` but the content does not actually wrap -->
<div id="align_content_center_wrap_singleline" style="width: 140px; height: 120px; flex-wrap: wrap; flex-direction: row; align-content: center;">
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
</div>
<div id="align_content_center_wrapped_negative_space" style="display: flex; flex-direction: column; width: 320px; height: 320px; border-width: 60px;">
<div style="display: flex; flex-direction: row; flex-wrap: wrap; align-content: center; justify-content: center; height: 10px;">
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
</div>
</div>
<div id="align_content_center_wrapped_negative_space_gap" style="display: flex; flex-direction: column; width: 320px; height: 320px; border-width: 60px;">
<div style="display: flex; flex-direction: row; flex-wrap: wrap; align-content: center; justify-content: center; height: 10px; gap: 10px;">
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
</div>
</div>
<!-- ALIGN CONTENT: SPACE-BETWEEN -->
<div id="align_content_space_between_nowrap" style="width: 140px; height: 120px; flex-direction: row; align-content: space-between;">
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
</div>
<div id="align_content_space_between_wrap" style="width: 140px; height: 120px; flex-wrap: wrap; flex-direction: row; align-content: space-between;">
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
</div>
<!-- This tests the case where `flex-wrap: wrap` but the content does not actually wrap -->
<div id="align_content_space_between_wrap_singleline" style="width: 140px; height: 120px; flex-wrap: wrap; flex-direction: row; align-content: space-between;">
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
</div>
<div id="align_content_space_between_wrapped_negative_space" style="display: flex; flex-direction: column; width: 320px; height: 320px; border-width: 60px;">
<div style="display: flex; flex-direction: row; flex-wrap: wrap; align-content: space-between; justify-content: center; height: 10px;">
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
</div>
</div>
<div id="align_content_space_between_wrapped_negative_space_row_reverse" style="display: flex; flex-direction: column; width: 320px; height: 320px; border-width: 60px;">
<div style="display: flex; flex-direction: row-reverse; flex-wrap: wrap; align-content: space-between; justify-content: center; height: 10px;">
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
</div>
</div>
<div id="align_content_space_between_wrapped_negative_space_gap" style="display: flex; flex-direction: column; width: 320px; height: 320px; border-width: 60px;">
<div style="display: flex; flex-direction: row; flex-wrap: wrap; align-content: space-between; justify-content: center; height: 10px; gap: 10px;">
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
</div>
</div>
<!-- ALIGN CONTENT: SPACE-AROUND -->
<div id="align_content_space_around_nowrap" style="width: 140px; height: 120px; flex-direction: row; align-content: space-around;">
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
</div>
<div id="align_content_space_around_wrap" style="width: 140px; height: 120px; flex-wrap: wrap; flex-direction: row; align-content: space-around;">
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
</div>
<!-- This tests the case where `flex-wrap: wrap` but the content does not actually wrap -->
<div id="align_content_space_around_wrap_singleline" style="width: 140px; height: 120px; flex-wrap: wrap; flex-direction: row; align-content: space-around;">
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
</div>
<div id="align_content_space_around_wrapped_negative_space" style="display: flex; flex-direction: column; width: 320px; height: 320px; border-width: 60px;">
<div style="display: flex; flex-direction: row; flex-wrap: wrap; align-content: space-around; justify-content: center; height: 10px;">
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
</div>
</div>
<div id="align_content_space_around_wrapped_negative_space_row_reverse" style="display: flex; flex-direction: column; width: 320px; height: 320px; border-width: 60px;">
<div style="display: flex; flex-direction: row-reverse; flex-wrap: wrap; align-content: space-around; justify-content: center; height: 10px;">
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
</div>
</div>
<div id="align_content_space_around_wrapped_negative_space_gap" style="display: flex; flex-direction: column; width: 320px; height: 320px; border-width: 60px;">
<div style="display: flex; flex-direction: row; flex-wrap: wrap; align-content: space-around; justify-content: center; height: 10px; gap: 10px;">
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
</div>
</div>
<!-- ALIGN CONTENT: SPACE-EVENLY -->
<div id="align_content_space_evenly_nowrap" style="width: 140px; height: 120px; flex-direction: row; align-content: space-evenly;">
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
</div>
<div id="align_content_space_evenly_wrap" style="width: 140px; height: 120px; flex-wrap: wrap; flex-direction: row; align-content: space-evenly;">
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
</div>
<!-- This tests the case where `flex-wrap: wrap` but the content does not actually wrap -->
<div id="align_content_space_evenly_wrap_singleline" style="width: 140px; height: 120px; flex-wrap: wrap; flex-direction: row; align-content: space-evenly;">
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
</div>
<div id="align_content_space_evenly_wrapped_negative_space" style="display: flex; flex-direction: column; width: 320px; height: 320px; border-width: 60px;">
<div style="display: flex; flex-direction: row; flex-wrap: wrap; align-content: space-evenly; justify-content: center; height: 10px;">
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
</div>
</div>
<div id="align_content_space_evenly_wrapped_negative_space_gap" style="display: flex; flex-direction: column; width: 320px; height: 320px; border-width: 60px;">
<div style="display: flex; flex-direction: row; flex-wrap: wrap; align-content: space-evenly; justify-content: center; height: 10px; gap: 10px;">
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
<div style="width: 80%; height: 20px; flex-shrink: 0;"></div>
</div>
</div>
<!-- ALIGN CONTENT: STRETCH -->
<div id="align_content_stretch" style="width: 150px; height: 100px; flex-wrap: wrap; flex-direction: column; align-content: stretch;">
<div style="width: 50px;"></div>
<div style="width: 50px;"></div>
@@ -265,6 +38,22 @@
<div style="width: 50px;"></div>
</div>
<div id="align_content_spacebetween" style="width: 130px; height: 100px; flex-wrap: wrap; flex-direction: row; align-content: space-between;">
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
</div>
<div id="align_content_spacearound" style="width: 140px; height: 120px; flex-wrap: wrap; flex-direction: row; align-content: space-around;">
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
</div>
<div id="align_content_stretch_row" style="width: 150px; height: 100px; flex-wrap: wrap; flex-direction: row; align-content: stretch;">
<div style="width: 50px;"></div>
<div style="width: 50px;"></div>
@@ -358,93 +147,4 @@
<div style="width:100px; height: 100px; align-items: center; flex-direction: row; align-content:stretch;">
<div style="height: 10px; width: 10px; align-content:stretch;"></div>
</div>
</div>
<div id="align_content_stretch_with_min_cross_axis" style="width: 500px; min-height: 500px; flex-direction: row; flex-wrap: wrap; align-content: stretch;">
<div style="width: 400px; height: 200px;"></div>
<div style="width: 400px; height: 200px;"></div>
</div>
<div id="align_content_stretch_with_max_cross_axis" style="width: 500px; max-height: 500px; flex-direction: row; flex-wrap: wrap; align-content: stretch;">
<div style="width: 400px; height: 200px;"></div>
<div style="width: 400px; height: 200px;"></div>
</div>
<div id="align_content_stretch_with_max_cross_axis_and_border_padding" style="width: 500px; max-height: 500px; flex-direction: row; flex-wrap: wrap; align-content: stretch; border: solid black 5px; padding: 2px">
<div style="width: 400px; height: 200px;"></div>
<div style="width: 400px; height: 200px;"></div>
</div>
<div id="align_content_space_evenly_with_min_cross_axis" style="width: 500px; min-height: 500px; flex-direction: row; flex-wrap: wrap; align-content: space-evenly;">
<div style="width: 400px; height: 200px;"></div>
<div style="width: 400px; height: 200px;"></div>
</div>
<div id="align_content_space_evenly_with_max_cross_axis" style="width: 500px; max-height: 500px; flex-direction: row; flex-wrap: wrap; align-content: space-evenly;">
<div style="width: 400px; height: 200px;"></div>
<div style="width: 400px; height: 200px;"></div>
</div>
<div id="align_content_space_evenly_with_max_cross_axis_violated" style="width: 500px; max-height: 300px; flex-direction: row; flex-wrap: wrap; align-content: space-evenly;">
<div style="width: 400px; height: 200px;"></div>
<div style="width: 400px; height: 200px;"></div>
</div>
<div id="align_content_space_evenly_with_max_cross_axis_violated_padding_and_border" style="width: 500px; max-height: 300px; flex-direction: row; flex-wrap: wrap; align-content: space-evenly; border: solid black 5px; padding: 2px">
<div style="width: 400px; height: 200px;"></div>
<div style="width: 400px; height: 200px;"></div>
</div>
<div id="align_content_space_around_and_align_items_flex_end_with_flex_wrap" style="width: 300px; height: 300px; flex-direction: row; flex-wrap: wrap; align-content: space-around;align-items: flex-end;">
<div style="height: 50px; width: 150px;"></div>
<div style="height: 100px; width: 120px;"></div>
<div style="height: 50px; width: 120px;"></div>
</div>
<div id="align_content_space_around_and_align_items_center_with_flex_wrap" style="width: 300px; height: 300px; flex-direction: row; flex-wrap: wrap; align-content: space-around;align-items: center;">
<div style="height: 50px; width: 150px;"></div>
<div style="height: 100px; width: 120px;"></div>
<div style="height: 50px; width: 120px;"></div>
</div>
<div id="align_content_space_around_and_align_items_flex_start_with_flex_wrap" style="width: 300px; height: 300px; flex-direction: row; flex-wrap: wrap; align-content: space-around;align-items: flex-start;">
<div style="height: 50px; width: 150px;"></div>
<div style="height: 100px; width: 120px;"></div>
<div style="height: 50px; width: 120px;"></div>
</div>
<div id="align_content_flex_start_stretch_doesnt_influence_line_box_dim" style="width: 400px; flex-direction: row; padding-top: 20px; padding-bottom: 20px; padding-left: 20px; padding-right: 20px;">
<div style="height: 100px; width: 100px; margin-right: 20px;"></div>
<div style="flex-direction: row; flex-wrap: wrap; flex-shrink: 1; flex-grow: 1; align-content: flex-start;">
<div style="height: 30px; width: 30px; margin-right: 20px;"></div>
<div style="height: 30px; width: 30px; margin-right: 20px;"></div>
<div style="height: 30px; width: 30px; margin-right: 20px;"></div>
<div style="height: 30px; width: 30px; margin-right: 20px;"></div>
<div style="height: 30px; width: 30px; margin-right: 20px;"></div>
</div>
<div style="height: 50px; width: 50px; margin-left: 20px;"></div>
</div>
<div id="align_content_stretch_stretch_does_influence_line_box_dim" style="width: 400px; flex-direction: row; padding-top: 20px; padding-bottom: 20px; padding-left: 20px; padding-right: 20px;">
<div style="height: 100px; width: 100px; margin-right: 20px;"></div>
<div style="flex-direction: row; flex-wrap: wrap; flex-shrink: 1; flex-grow: 1; align-content: stretch;">
<div style="height: 30px; width: 30px; margin-right: 20px;"></div>
<div style="height: 30px; width: 30px; margin-right: 20px;"></div>
<div style="height: 30px; width: 30px; margin-right: 20px;"></div>
<div style="height: 30px; width: 30px; margin-right: 20px;"></div>
<div style="height: 30px; width: 30px; margin-right: 20px;"></div>
</div>
<div style="height: 50px; width: 50px; margin-left: 20px;"></div>
</div>
<div id="align_content_space_evenly_stretch_does_influence_line_box_dim" style="width: 400px; flex-direction: row; padding-top: 20px; padding-bottom: 20px; padding-left: 20px; padding-right: 20px;">
<div style="height: 100px; width: 100px; margin-right: 20px;"></div>
<div style="flex-direction: row; flex-wrap: wrap; flex-shrink: 1; flex-grow: 1; align-content: stretch;">
<div style="height: 30px; width: 30px; margin-right: 20px;"></div>
<div style="height: 30px; width: 30px; margin-right: 20px;"></div>
<div style="height: 30px; width: 30px; margin-right: 20px;"></div>
<div style="height: 30px; width: 30px; margin-right: 20px;"></div>
<div style="height: 30px; width: 30px; margin-right: 20px;"></div>
</div>
<div style="height: 50px; width: 50px; margin-left: 20px;"></div>
</div>
</div>

View File

@@ -26,8 +26,7 @@
</div>
</div>
<div id="align_baseline_child_multiline"
style="width: 100px; height: 100px; flex-direction:row; align-items: baseline;">
<div id="align_baseline_child_multiline" style="width: 100px; height: 100px; flex-direction:row; align-items: baseline;">
<div style="width: 50px; height: 60px;"></div>
<div style="width: 50px; height: 25px;flex-wrap:wrap;flex-direction:row;">
<div style="width: 25px; height: 20px;"></div>
@@ -37,8 +36,7 @@
</div>
</div>
<div id="align_baseline_child_multiline_override"
style="width: 100px; height: 100px; flex-direction:row; align-items: baseline;">
<div id="align_baseline_child_multiline_override" style="width: 100px; height: 100px; flex-direction:row; align-items: baseline;">
<div style="width: 50px; height: 60px;"></div>
<div style="width: 50px; height: 25px;flex-wrap:wrap;flex-direction:row;">
<div style="width: 25px; height: 20px;"></div>
@@ -48,8 +46,7 @@
</div>
</div>
<div id="align_baseline_child_multiline_no_override_on_secondline"
style="width: 100px; height: 100px; flex-direction:row; align-items: baseline;">
<div id="align_baseline_child_multiline_no_override_on_secondline" style="width: 100px; height: 100px; flex-direction:row; align-items: baseline;">
<div style="width: 50px; height: 60px;"></div>
<div style="width: 50px; height: 25px;flex-wrap:wrap;flex-direction:row;">
<div style="width: 25px; height: 20px;"></div>
@@ -74,8 +71,7 @@
</div>
</div>
<div id="align_baseline_double_nested_child"
style="width: 100px; height: 100px; flex-direction:row; align-items: baseline;">
<div id="align_baseline_double_nested_child" style="width: 100px; height: 100px; flex-direction:row; align-items: baseline;">
<div style="width: 50px; height: 50px;">
<div style="width: 50px; height: 20px;"></div>
</div>
@@ -97,16 +93,14 @@
</div>
</div>
<div id="align_baseline_child_padding"
style="width: 100px; height: 100px; padding:5px; flex-direction:row; align-items: baseline;">
<div id="align_baseline_child_padding" style="width: 100px; height: 100px; padding:5px; flex-direction:row; align-items: baseline;">
<div style="width: 50px; height: 50px;"></div>
<div style="width: 50px; height: 20px;padding:5px;">
<div style="width: 50px; height: 10px;"></div>
</div>
</div>
<div id="align_baseline_multiline"
style="width: 100px; height: 100px; flex-direction:row; align-items: baseline;flex-wrap:wrap;">
<div id="align_baseline_multiline" style="width: 100px; height: 100px; flex-direction:row; align-items: baseline;flex-wrap:wrap;">
<div style="width: 50px; height: 50px;"></div>
<div style="width: 50px; height: 20px;">
<div style="width: 50px; height: 10px;"></div>
@@ -118,8 +112,7 @@
</div>
<!-- TODO: Yoga's behavior is no longer in line with Chromium -->
<div id="align_baseline_multiline_column" data-disabled="true"
style="width: 100px; height: 100px; flex-direction:column; align-items: baseline;flex-wrap:wrap;">
<div id="align_baseline_multiline_column" data-disabled="true" style="width: 100px; height: 100px; flex-direction:column; align-items: baseline;flex-wrap:wrap;">
<div style="width: 50px; height: 50px;"></div>
<div style="width: 30px; height: 50px;">
<div style="width: 20px; height: 20px;"></div>
@@ -131,8 +124,7 @@
</div>
<!-- TODO: Yoga's behavior is no longer in line with Chromium -->
<div id="align_baseline_multiline_column2" data-disabled="true"
style="width: 100px; height: 100px; flex-direction:column; align-items: baseline;flex-wrap:wrap;">
<div id="align_baseline_multiline_column2" data-disabled="true" style="width: 100px; height: 100px; flex-direction:column; align-items: baseline;flex-wrap:wrap;">
<div style="width: 50px; height: 50px;flex-direction:column;"></div>
<div style="width: 30px; height: 50px;flex-direction:column;">
<div style="width: 20px; height: 20px;flex-direction:column;"></div>
@@ -143,8 +135,7 @@
<div style="width: 50px; height: 20px;flex-direction:column;"></div>
</div>
<div id="align_baseline_multiline_row_and_column"
style="width: 100px; height: 100px; flex-direction:row; align-items: baseline;flex-wrap:wrap;">
<div id="align_baseline_multiline_row_and_column" style="width: 100px; height: 100px; flex-direction:row; align-items: baseline;flex-wrap:wrap;">
<div style="width: 50px; height: 50px;"></div>
<div style="width: 50px; height: 50px;flex-direction:column;">
<div style="width: 50px; height: 10px;"></div>
@@ -155,36 +146,31 @@
<div style="width: 50px; height: 20px;"></div>
</div>
<div id="align_items_center_child_with_margin_bigger_than_parent"
style="height: 52px; width: 52px; align-items: center; justify-content: center;">
<div id="align_items_center_child_with_margin_bigger_than_parent" style="height: 52px; width: 52px; align-items: center; justify-content: center;">
<div style="align-items: center;">
<div style="width: 52px; height: 52px; margin-left: 10px; margin-right: 10px;"></div>
</div>
</div>
<div id="align_items_flex_end_child_with_margin_bigger_than_parent"
style="height: 52px; width: 52px; align-items: center; justify-content: center;">
<div id="align_items_flex_end_child_with_margin_bigger_than_parent" style="height: 52px; width: 52px; align-items: center; justify-content: center;">
<div style="align-items: flex-end;">
<div style="width: 52px; height: 52px; margin-left: 10px; margin-right: 10px;"></div>
</div>
</div>
<div id="align_items_center_child_without_margin_bigger_than_parent"
style="height: 52px; width: 52px; align-items: center; justify-content: center;">
<div id="align_items_center_child_without_margin_bigger_than_parent" style="height: 52px; width: 52px; align-items: center; justify-content: center;">
<div style="align-items: center;">
<div style="width: 72px; height: 72px;"></div>
</div>
</div>
<div id="align_items_flex_end_child_without_margin_bigger_than_parent"
style="height: 52px; width: 52px; align-items: center; justify-content: center;">
<div id="align_items_flex_end_child_without_margin_bigger_than_parent" style="height: 52px; width: 52px; align-items: center; justify-content: center;">
<div style="align-items: flex-end;">
<div style="width: 72px; height: 72px;"></div>
</div>
</div>
<div id="align_center_should_size_based_on_content"
style="width: 100px; height: 100px; align-items: center; margin-top: 20px;">
<div id="align_center_should_size_based_on_content" style="width: 100px; height: 100px; align-items: center; margin-top: 20px;">
<div style="flex-grow: 0; flex-shrink: 1; justify-content: center;">
<div style="flex-grow: 1; flex-shrink: 1;">
<div style="width: 20px; height: 20px;"></div>
@@ -192,8 +178,7 @@
</div>
</div>
<div id="align_stretch_should_size_based_on_parent"
style="width: 100px; height: 100px; align-items: stretch; margin-top: 20px;">
<div id="align_stretch_should_size_based_on_parent" style="width: 100px; height: 100px; align-items: stretch; margin-top: 20px;">
<div style="flex-grow: 0; flex-shrink: 1; justify-content: center;">
<div style="flex-grow: 1; flex-shrink: 1;">
<div style="width: 20px; height: 20px;"></div>
@@ -224,19 +209,3 @@
</div>
</div>
</div>
<div id="align_flex_end_with_row_reverse"
style="width: 100px; height: 75px; align-items: flex-end; flex-direction: column; flex-wrap: wrap;">
<div style="position:relative; width:50px; height:50px; margin-right: 5px; margin-left: 3px;">
</div>
<div style="position:relative; width:50px; height:50px;">
</div>
</div>
<div id="align_stretch_with_row_reverse"
style="width: 100px; height: 75px; align-items: stretch; flex-direction: column; flex-wrap: wrap;">
<div style="position:relative; width:50px; height:50px; margin-right: 5px; margin-left: 3px;">
</div>
<div style="position:relative; width:50px; height:50px;">
</div>
</div>

View File

@@ -1,15 +0,0 @@
<!-- TODO: aspect-ratio behavior is inconsistent with Web -->
<div id="aspect_ratio_does_not_stretch_cross_axis_dim" data-disabled="true" style="width: 300px; height: 300px;">
<div style="flex: 1; overflow: scroll;">
<div style="flex-direction: row;">
<div style="flex: 2; aspect-ratio: 1;"></div>
<div style="width: 5px"></div>
<div style="flex: 1">
<div style="flex: 1; aspect-ratio: 1;">
<div style="width: 5px"></div>
<div style="flex: 1; aspect-ratio: 1;"></div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -33,375 +33,3 @@
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
<div id="flex_direction_row_reverse_margin_left"
style="height: 100px; width: 100px; flex-direction: row-reverse; margin-left: 100px;">
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
<!-- gentest.rb will swap margin-start to margin-left. This is needed to use YGEdgeStart instead of YGEdgeLeft in the generated tests -->
<div id="flex_direction_row_reverse_margin_start"
style="height: 100px; width: 100px; flex-direction: row-reverse; margin-start: 100px;">
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
<div id="flex_direction_row_reverse_margin_right"
style="height: 100px; width: 100px; flex-direction: row-reverse; margin-right: 100px;">
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
<div id="flex_direction_row_reverse_margin_end"
style="height: 100px; width: 100px; flex-direction: row-reverse; margin-end: 100px;">
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
<div id="flex_direction_column_reverse_margin_top"
style="height: 100px; width: 100px; flex-direction: column-reverse; margin-top: 100px;">
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
<div id="flex_direction_column_reverse_margin_bottom"
style="height: 100px; width: 100px; flex-direction: column-reverse; margin-bottom: 100px;">
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
<div id="flex_direction_row_reverse_padding_left"
style="height: 100px; width: 100px; flex-direction: row-reverse; padding-left: 100px;">
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
<div id="flex_direction_row_reverse_padding_start"
style="height: 100px; width: 100px; flex-direction: row-reverse; padding-start: 100px;">
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
<div id="flex_direction_row_reverse_padding_right"
style="height: 100px; width: 100px; flex-direction: row-reverse; padding-right: 100px;">
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
<div id="flex_direction_row_reverse_padding_end"
style="height: 100px; width: 100px; flex-direction: row-reverse; padding-end: 100px;">
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
<div id="flex_direction_column_reverse_padding_top"
style="height: 100px; width: 100px; flex-direction: column-reverse; padding-top: 100px;">
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
<div id="flex_direction_column_reverse_padding_bottom"
style="height: 100px; width: 100px; flex-direction: column-reverse; padding-bottom: 100px;">
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
<div id="flex_direction_row_reverse_border_left"
style="height: 100px; width: 100px; flex-direction: row-reverse; border-left-width: 100px;">
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
<div id="flex_direction_row_reverse_border_start"
style="height: 100px; width: 100px; flex-direction: row-reverse; border-start-width: 100px;">
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
<div id="flex_direction_row_reverse_border_right"
style="height: 100px; width: 100px; flex-direction: row-reverse; border-right-width: 100px;">
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
<div id="flex_direction_row_reverse_border_end"
style="height: 100px; width: 100px; flex-direction: row-reverse; border-end-width: 100px;">
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
<div id="flex_direction_column_reverse_border_top"
style="height: 100px; width: 100px; flex-direction: column-reverse; border-top-width: 100px;">
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
<div id="flex_direction_column_reverse_border_bottom"
style="height: 100px; width: 100px; flex-direction: column-reverse; border-bottom-width: 100px;">
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
<div id="flex_direction_row_reverse_pos_left" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: row-reverse; left: 100px;">
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_row_reverse_pos_start" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: row-reverse; start: 100px;">
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_row_reverse_pos_right" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: row-reverse; right: 100px;">
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_row_reverse_pos_end" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: row-reverse; end: 100px;">
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_column_reverse_pos_top" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: column-reverse; top: 100px;">
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_column_reverse_pos_bottom" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: column-reverse; bottom: 100px;">
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_row_reverse_inner_pos_left" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: row-reverse; ">
<div style="width: 10px; height: 10px; position: absolute; left: 10px"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_row_reverse_inner_pos_right" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: row-reverse; ">
<div style="width: 10px; height: 10px; position: absolute; right: 10px"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_col_reverse_inner_pos_top" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: column-reverse; ">
<div style="width: 10px; height: 10px; position: absolute; top: 10px"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_col_reverse_inner_pos_bottom" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: column-reverse; ">
<div style="width: 10px; height: 10px; position: absolute; bottom: 10px"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_row_reverse_inner_pos_start" data-disabled="true" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: row-reverse; ">
<div style="width: 10px; height: 10px; position: absolute; start: 10px"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_row_reverse_inner_pos_end" data-disabled="true" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: row-reverse; ">
<div style="width: 10px; height: 10px; position: absolute; end: 10px"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_row_reverse_inner_margin_left" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: row-reverse; ">
<div style="width: 10px; height: 10px; position: absolute; margin-left: 10px"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_row_reverse_inner_margin_right" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: row-reverse; ">
<div style="width: 10px; height: 10px; position: absolute; margin-right: 10px"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_col_reverse_inner_margin_top" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: column-reverse; ">
<div style="width: 10px; height: 10px; position: absolute; margin-top: 10px"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_col_reverse_inner_margin_bottom" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: column-reverse; ">
<div style="width: 10px; height: 10px; position: absolute; margin-bottom: 10px"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_row_reverse_inner_marign_start" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: row-reverse; ">
<div style="width: 10px; height: 10px; position: absolute; margin-start: 10px"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_row_reverse_inner_margin_end" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: row-reverse; ">
<div style="width: 10px; height: 10px; position: absolute; margin-end: 10px"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_row_reverse_inner_border_left" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: row-reverse; ">
<div style="width: 10px; height: 10px; position: absolute; border-left: 10px solid black"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_row_reverse_inner_border_right" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: row-reverse; ">
<div style="width: 10px; height: 10px; position: absolute; border-right: 10px solid black"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_col_reverse_inner_border_top" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: column-reverse; ">
<div style="width: 10px; height: 10px; position: absolute; border-top: 10px solid black"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_col_reverse_inner_border_bottom" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: column-reverse; ">
<div style="width: 10px; height: 10px; position: absolute; border-bottom: 10px solid black"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_row_reverse_inner_border_start" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: row-reverse; ">
<div style="width: 10px; height: 10px; position: absolute; border-start: 10px solid black"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_row_reverse_inner_border_end" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: row-reverse; ">
<div style="width: 10px; height: 10px; position: absolute; border-end: 10px solid black"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_row_reverse_inner_padding_left" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: row-reverse; ">
<div style="width: 10px; height: 10px; position: absolute; padding-left: 10px"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_row_reverse_inner_padding_right" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: row-reverse; ">
<div style="width: 10px; height: 10px; position: absolute; padding-right: 10px"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_col_reverse_inner_padding_top" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: column-reverse; ">
<div style="width: 10px; height: 10px; position: absolute; padding-top: 10px"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_col_reverse_inner_padding_bottom" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: column-reverse; ">
<div style="width: 10px; height: 10px; position: absolute; padding-bottom: 10px"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_row_reverse_inner_padding_start" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: row-reverse; ">
<div style="width: 10px; height: 10px; position: absolute; padding-start: 10px"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_row_reverse_inner_padding_end" style="height: 100px; width: 100px;">
<div style="height: 100px; width: 100px; flex-direction: row-reverse; ">
<div style="width: 10px; height: 10px; position: absolute; padding-end: 10px"></div>
<div style="width: 10px;"></div>
<div style="width: 10px;"></div>
</div>
</div>
<div id="flex_direction_alternating_with_percent" style="height: 300px; width: 200px; flex-direction: column;">
<div style="height: 50%; width: 50%; left: 10%; top: 10%; flex-direction: row;">
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More