diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 613b4f43e..43775f49f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -209,3 +209,19 @@ jobs: name: Build benchmarks (w/yoga) env: RUSTFLAGS: "-C opt-level=0" + + build-c-bindings: + name: Build C Bindings + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - run: | + cd bindings/c + cargo build + name: Build ctaffy library + - run: | + cd bindings/c/examples + ./compile_basic.sh + LD_LIBRARY_PATH=$LD_LIBRARY_PATH:../../../target/debug ./basic + name: Build C Bindings diff --git a/Cargo.toml b/Cargo.toml index 01ea1ee50..6336c2d9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,4 +72,4 @@ path = "benches/dummy_benchmark.rs" harness = false [workspace] -members = ["scripts/gentest", "scripts/format-fixtures", "scripts/import-yoga-tests", "benches"] +members = ["scripts/gentest", "scripts/format-fixtures", "scripts/import-yoga-tests", "benches", "bindings/c"] diff --git a/bindings/c/.gitignore b/bindings/c/.gitignore new file mode 100644 index 000000000..f03d2b8d8 --- /dev/null +++ b/bindings/c/.gitignore @@ -0,0 +1 @@ +examples/basic \ No newline at end of file diff --git a/bindings/c/Cargo.toml b/bindings/c/Cargo.toml new file mode 100644 index 000000000..d861825d5 --- /dev/null +++ b/bindings/c/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "ctaffy" +version = "0.0.1" +authors = [ + "Nico Burns ", +] +edition = "2021" +include = ["src/**/*", "Cargo.toml"] +description = "C bindings to Taffy (A flexible UI layout library)" +repository = "https://github.com/DioxusLabs/taffy" +license = "MIT" + +[lib] +name = "ctaffy" +crate-type = ["staticlib", "cdylib", "rlib"] + +[dependencies] +taffy = { path = "../.." } \ No newline at end of file diff --git a/bindings/c/README.md b/bindings/c/README.md new file mode 100644 index 000000000..97ccbce35 --- /dev/null +++ b/bindings/c/README.md @@ -0,0 +1,97 @@ +# C API for Taffy (ctaffy) + +Taffy is a flexible, high-performance, cross-platform UI layout library written in Rust. + +This directory contains C bindings for Taffy. The API is a pure C API (no C++ features are used), and is designed to be easy to build other language bindings on top of. In addition to the documentation below, you may to read through the header file (`include/taffy.h`). + +## Examples + +There are readable examples in the examples directory. + +Assuming you have Rust and Cargo installed (and a C compiler), then this should work to run the basic example: + +```bash +git clone https://github.com/DioxusLabs/taffy.git +cd taffy/ctaffy/examples +./compile-basic.sh +./basic +``` + +## Naming Conventions + +- Everything in the Taffy C API is prefixed with `Taffy`, except enum variant names which are prefixed with `TAFFY_` +- Structs and Enums are named in UpperCamelCase (e.g. `TaffyTree`, `TaffyStyle`) +- Functions begin with the name of the struct they apply to, followed by an underscore, followed by the name of the function in UpperCamelCase (e.g. `TaffyTree_New`, `TaffyStyle_SetFlexBasis`) +- Enum variants are SCREAMING_SNAKE_CASE + +## Error Handling + +Error handling is managed by the use of return codes and "result" structs. All functions in the API return either an `enum TaffyReturnCode` or one of the `struct Taffy*Result` structs (or `void` in the case of infallible operations that don't return anything). + +### Return codes + +Error handling is managed by the use of an enum `TaffyReturnCode`: + +```c +typedef enum TaffyReturnCode { + TAFFY_RETURN_CODE_OK, + TAFFY_RETURN_CODE_NULL_STYLE_POINTER, + TAFFY_RETURN_CODE_NULL_TREE_POINTER, + // ... (see header file for full definition) +} TaffyReturnCode; +``` + +`TAFFY_RETURN_CODE_OK` indicates that the operation succeeded. All other variant indicate + +### Result structs + +"Result structs" are used for functions that need to return another value in addition to a `TaffyReturnCode` indicating success/failure (such as style getters which need to return the relevant style value). As C doesn't support generic structs, there are several "Result structs": one for each type of value. But each struct follows the same structure as the following example (varying only in the name of the struct and the type of the `value` field): + + + + + + + +
TaffyIntResultTaffyDimensionResult
+ +```c +typedef struct TaffyIntResult { + enum TaffyReturnCode return_code; + int32_t value; +} TaffyIntResult; +``` + + + +```c +typedef struct TaffyDimensionResult { + enum TaffyReturnCode return_code; + struct TaffyDimension value; +} TaffyDimensionResult; +``` + +
+ +Functions that return Result structs will either return a `TAFFY_RETURN_CODE_OK` along with a meaningful value, or a error variant of `TaffyReturnCode` along with + +## API Usage + +### Tree Creation and Manipulation + +The `TaffyTree` struct is the entrypoint to the Taffy C API and represents an entire tree of UI nodes. Taffy uses arena allocation, so the `TaffyTree` owns the entire tree of nodes at all times. + +You only ever get access to a `TaffyNodeId` handle which can be used to manipulate the structure of the tree, or pointers to `TaffyStyle` object (which can be used to set styles on a Node, but are considered borrowed pointers and thus must only be held temporarily). + +#### Lifecycle + +- `TaffyTree_New` allocates a new `TaffyTree` and returns an owned pointer to it. +- `TaffyTree_Free` can be used to free the tree once you are done with it. + +All other functions in the API which accept a pointer to a `TaffyTree` have borrowing semantics: they access the tree during the duration of the function (and if the pointer is not a `const` pointer, may modify the tree), but will not store the pointer or take ownership of the tree. + +#### Node creation and manipulation + +- `TaffyTree_NewNode` creates a new Node within the tree and returns a `TaffyNodeId` handle to it. The node will initially have the default styles + +### Setting styles on node diff --git a/bindings/c/cbindgen.toml b/bindings/c/cbindgen.toml new file mode 100644 index 000000000..f7972e9db --- /dev/null +++ b/bindings/c/cbindgen.toml @@ -0,0 +1,22 @@ +language = "c" +cpp_compat = true +documentation_style = "c99" +line_length = 120 + +[enum] +rename_variants = "QualifiedScreamingSnakeCase" + +[export] +include = ["TaffyNodeId"] + +[export.rename] +TaffyResult_f32 = "TaffyFloatResult" +TaffyResult_i32 = "TaffyIntResult" +TaffyResult_TaffyNodeId = "TaffyNodeIdResult" +TaffyResult_TaffyDimension = "TaffyDimensionResult" +TaffyResult_TaffyGridPlacement = "TaffyGridPlacementResult" +TaffyResult_TaffyStyleMutRef = "TaffyStyleMutRefResult" + +[parse.expand] +crates = ["ctaffy"] + diff --git a/bindings/c/examples/basic.c b/bindings/c/examples/basic.c new file mode 100644 index 000000000..3da5c2a09 --- /dev/null +++ b/bindings/c/examples/basic.c @@ -0,0 +1,41 @@ +#include +#include +#include +#include + +#include "taffy.h" + +int main() { + // Create tree + TaffyTree *tree = TaffyTree_New(); + + // Create child (+set styles) + TaffyNodeId child = TaffyTree_NewNode(tree).value; + TaffyStyle *child_style = TaffyTree_GetStyleMut(tree, child).value; + TaffyStyle_SetWidth(child_style, 0.5, TAFFY_UNIT_PERCENT); + TaffyStyle_SetHeight(child_style, 0, TAFFY_UNIT_AUTO); + + // Create parent (+set styles) + TaffyNodeId parent = TaffyTree_NewNode(tree).value; + TaffyStyle *parent_style = TaffyTree_GetStyleMut(tree, parent).value; + TaffyStyle_SetWidth(parent_style, 100, TAFFY_UNIT_LENGTH); + TaffyStyle_SetHeight(parent_style, 100, TAFFY_UNIT_LENGTH); + TaffyStyle_SetJustifyContent(parent_style, TAFFY_ALIGN_CONTENT_CENTER); + + // Setup parent-child relationship + TaffyTree_AppendChild(tree, parent, child); + + // Compute layout (100x100 viewport) + printf("\nCompute layout with 100x100 viewport:\n"); + TaffyTree_ComputeLayout(tree, parent, 100, 100); + TaffyTree_PrintTree(tree, parent); + + // Compute layout (infinite viewport) + printf("\nCompute layout with infinite viewport:\n"); + TaffyTree_ComputeLayout(tree, parent, INFINITY, INFINITY); + TaffyTree_PrintTree(tree, parent); + + // Free tree + TaffyTree_Free(tree); + return 0; +} diff --git a/bindings/c/examples/compile_basic.sh b/bindings/c/examples/compile_basic.sh new file mode 100755 index 000000000..02b90a64b --- /dev/null +++ b/bindings/c/examples/compile_basic.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +set -ex + +cargo build --manifest-path ../Cargo.toml +gcc -O3 -DDEBUG -o basic basic.c -std=c99 -Wall -I../include -L../../../target/debug -lctaffy diff --git a/bindings/c/generate-bindings.sh b/bindings/c/generate-bindings.sh new file mode 100755 index 000000000..bb8f526af --- /dev/null +++ b/bindings/c/generate-bindings.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +cbindgen --crate ctaffy --output include/taffy.h \ No newline at end of file diff --git a/bindings/c/include/taffy.h b/bindings/c/include/taffy.h new file mode 100644 index 000000000..72f47139d --- /dev/null +++ b/bindings/c/include/taffy.h @@ -0,0 +1,576 @@ +#include +#include +#include +#include + +// Sets the distribution of space between and around content items +// For Flexbox it controls alignment in the cross axis +// For Grid it controls alignment in the block axis +// +// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/align-content) +typedef enum TaffyAlignContent { + // Items are aligned according to their algorithm-specific default value + // This is equivalent to not setting a value in CSS + TAFFY_ALIGN_CONTENT_NORMAL, + // Items are packed toward the start of the axis + TAFFY_ALIGN_CONTENT_START, + // Items are packed toward the end of the axis + TAFFY_ALIGN_CONTENT_END, + // Items are packed towards the flex-relative start of the axis. + // + // For flex containers with flex_direction RowReverse or ColumnReverse this is equivalent + // to End. In all other cases it is equivalent to Start. + TAFFY_ALIGN_CONTENT_FLEX_START, + // Items are packed towards the flex-relative end of the axis. + // + // For flex containers with flex_direction RowReverse or ColumnReverse this is equivalent + // to Start. In all other cases it is equivalent to End. + TAFFY_ALIGN_CONTENT_FLEX_END, + // Items are centered around the middle of the axis + TAFFY_ALIGN_CONTENT_CENTER, + // Items are stretched to fill the container + TAFFY_ALIGN_CONTENT_STRETCH, + // The first and last items are aligned flush with the edges of the container (no gap) + // The gap between items is distributed evenly. + TAFFY_ALIGN_CONTENT_SPACE_BETWEEN, + // The gap between the first and last items is exactly THE SAME as the gap between items. + // The gaps are distributed evenly + TAFFY_ALIGN_CONTENT_SPACE_EVENLY, + // The gap between the first and last items is exactly HALF the gap between items. + // The gaps are distributed evenly in proportion to these ratios. + TAFFY_ALIGN_CONTENT_SPACE_AROUND, +} TaffyAlignContent; + +// Used to control how child nodes are aligned. +// For Flexbox it controls alignment in the cross axis +// For Grid it controls alignment in the block axis +// +// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/align-items) +typedef enum TaffyAlignItems { + // Items are aligned according to their algorithm-specific default value + // This is equivalent to not setting a value in CSS + TAFFY_ALIGN_ITEMS_NORMAL, + // Items are packed toward the start of the axis + TAFFY_ALIGN_ITEMS_START, + // Items are packed toward the end of the axis + TAFFY_ALIGN_ITEMS_END, + // Items are packed towards the flex-relative start of the axis. + // + // For flex containers with flex_direction RowReverse or ColumnReverse this is equivalent + // to End. In all other cases it is equivalent to Start. + TAFFY_ALIGN_ITEMS_FLEX_START, + // Items are packed towards the flex-relative end of the axis. + // + // For flex containers with flex_direction RowReverse or ColumnReverse this is equivalent + // to Start. In all other cases it is equivalent to End. + TAFFY_ALIGN_ITEMS_FLEX_END, + // Items are packed along the center of the cross axis + TAFFY_ALIGN_ITEMS_CENTER, + // Items are aligned such as their baselines align + TAFFY_ALIGN_ITEMS_BASELINE, + // Stretch to fill the container + TAFFY_ALIGN_ITEMS_STRETCH, +} TaffyAlignItems; + +// Sets the layout used for the children of this node +// +// The default values depends on on which feature flags are enabled. The order of precedence is: Flex, Grid, Block, None. +typedef enum TaffyDisplay { + // The children will follow the block layout algorithm + TAFFY_DISPLAY_BLOCK, + // The children will follow the flexbox layout algorithm + TAFFY_DISPLAY_FLEX, + // The children will follow the CSS Grid layout algorithm + TAFFY_DISPLAY_GRID, + // The children will not be laid out, and will follow absolute positioning + TAFFY_DISPLAY_NONE, +} TaffyDisplay; + +typedef enum TaffyEdge { + // The top edge of the box + TAFFY_EDGE_TOP, + // The bottom edge of the box + TAFFY_EDGE_BOTTOM, + // The left edge of the box + TAFFY_EDGE_LEFT, + // The right edge of the box + TAFFY_EDGE_RIGHT, + // Both the top and bottom edges of the box + TAFFY_EDGE_VERTICAL, + // Both the left and right edges of the box + TAFFY_EDGE_HORIZONTAL, + // All four edges of the box + TAFFY_EDGE_ALL, +} TaffyEdge; + +// The direction of the flexbox layout main axis. +// +// There are always two perpendicular layout axes: main (or primary) and cross (or secondary). +// Adding items will cause them to be positioned adjacent to each other along the main axis. +// By varying this value throughout your tree, you can create complex axis-aligned layouts. +// +// Items are always aligned relative to the cross axis, and justified relative to the main axis. +// +// The default behavior is [`FlexDirection::Row`]. +// +// [Specification](https://www.w3.org/TR/css-flexbox-1/#flex-direction-property) +typedef enum TaffyFlexDirection { + // Defines +x as the main axis + // + // Items will be added from left to right in a row. + TAFFY_FLEX_DIRECTION_ROW, + // Defines +y as the main axis + // + // Items will be added from top to bottom in a column. + TAFFY_FLEX_DIRECTION_COLUMN, + // Defines -x as the main axis + // + // Items will be added from right to left in a row. + TAFFY_FLEX_DIRECTION_ROW_REVERSE, + // Defines -y as the main axis + // + // Items will be added from bottom to top in a column. + TAFFY_FLEX_DIRECTION_COLUMN_REVERSE, +} TaffyFlexDirection; + +// Controls whether flex items are forced onto one line or can wrap onto multiple lines. +// +// Defaults to [`FlexWrap::NoWrap`] +// +// [Specification](https://www.w3.org/TR/css-flexbox-1/#flex-wrap-property) +typedef enum TaffyFlexWrap { + // Items will not wrap and stay on a single line + TAFFY_FLEX_WRAP_NO_WRAP, + // Items will wrap according to this item's [`FlexDirection`] + TAFFY_FLEX_WRAP_WRAP, + // Items will wrap in the opposite direction to this item's [`FlexDirection`] + TAFFY_FLEX_WRAP_WRAP_REVERSE, +} TaffyFlexWrap; + +// Controls whether grid items are placed row-wise or column-wise. And whether the sparse or dense packing algorithm is used. +// +// The "dense" packing algorithm attempts to fill in holes earlier in the grid, if smaller items come up later. +// This may cause items to appear out-of-order, when doing so would fill in holes left by larger items. +// +// Defaults to [`GridAutoFlow::Row`] +// +// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow) +typedef enum TaffyGridAutoFlow { + // Items are placed by filling each row in turn, adding new rows as necessary + TAFFY_GRID_AUTO_FLOW_ROW, + // Items are placed by filling each column in turn, adding new columns as necessary. + TAFFY_GRID_AUTO_FLOW_COLUMN, + // Combines `Row` with the dense packing algorithm. + TAFFY_GRID_AUTO_FLOW_ROW_DENSE, + // Combines `Column` with the dense packing algorithm. + TAFFY_GRID_AUTO_FLOW_COLUMN_DENSE, +} TaffyGridAutoFlow; + +typedef enum TaffyMeasureMode { + // A none value (used to unset optional fields) + TAFFY_MEASURE_MODE_EXACT, + // Fixed Length (pixel) value + TAFFY_MEASURE_MODE_FIT_CONTENT, + // Percentage value + TAFFY_MEASURE_MODE_MIN_CONTENT, + // Min-content size + TAFFY_MEASURE_MODE_MAX_CONTENT, +} TaffyMeasureMode; + +// How children overflowing their container should affect layout +// +// In CSS the primary effect of this property is to control whether contents of a parent container that overflow that container should +// be displayed anyway, be clipped, or trigger the container to become a scroll container. However it also has secondary effects on layout, +// the main ones being: +// +// - The automatic minimum size Flexbox/CSS Grid items with non-`Visible` overflow is `0` rather than being content based +// - `Overflow::Scroll` nodes have space in the layout reserved for a scrollbar (width controlled by the `scrollbar_width` property) +// +// In Taffy, we only implement the layout related secondary effects as we are not concerned with drawing/painting. The amount of space reserved for +// a scrollbar is controlled by the `scrollbar_width` property. If this is `0` then `Scroll` behaves identically to `Hidden`. +// +// +typedef enum TaffyOverflow { + // The automatic minimum size of this node as a flexbox/grid item should be based on the size of it's content. + TAFFY_OVERFLOW_VISIBLE, + // The automatic minimum size of this node as a flexbox/grid item should be based on the size of its content. + // Content that overflows this node should *not* contribute to the scroll region of its parent. + TAFFY_OVERFLOW_CLIP, + // The automatic minimum size of this node as a flexbox/grid item should be `0`. + TAFFY_OVERFLOW_HIDDEN, + // The automatic minimum size of this node as a flexbox/grid item should be `0`. Additionally, space should be reserved + // for a scrollbar. The amount of space reserved is controlled by the `scrollbar_width` property. + TAFFY_OVERFLOW_SCROLL, +} TaffyOverflow; + +// The positioning strategy for this item. +// +// This controls both how the origin is determined for the [`Style::position`] field, +// and whether or not the item will be controlled by flexbox's layout algorithm. +// +// WARNING: this enum follows the behavior of [CSS's `position` property](https://developer.mozilla.org/en-US/docs/Web/CSS/position), +// which can be unintuitive. +// +// [`Position::Relative`] is the default value, in contrast to the default behavior in CSS. +typedef enum TaffyPosition { + // The offset is computed relative to the final position given by the layout algorithm. + // Offsets do not affect the position of any other items; they are effectively a correction factor applied at the end. + TAFFY_POSITION_RELATIVE, + // The offset is computed relative to this item's closest positioned ancestor, if any. + // Otherwise, it is placed relative to the origin. + // No space is created for the item in the page layout, and its size will not be altered. + // + // WARNING: to opt-out of layouting entirely, you must use [`Display::None`] instead on your [`Style`] object. + TAFFY_POSITION_ABSOLUTE, +} TaffyPosition; + +typedef enum TaffyReturnCode { + // Operation suceeded + TAFFY_RETURN_CODE_OK, + // The style pointer passed was null + TAFFY_RETURN_CODE_NULL_STYLE_POINTER, + // The tree pointer passed was null + TAFFY_RETURN_CODE_NULL_TREE_POINTER, + // The node referenced by the node id passed does not exist + TAFFY_RETURN_CODE_INVALID_NODE_ID, + // An enum value was specified that was outside the range of valid value for this enum + TAFFY_RETURN_CODE_INVALID_ENUM_VALUE, + // A Points unit was specified but is not valid in this context + TAFFY_RETURN_CODE_INVALID_NONE, + // A Points unit was specified but is not valid in this context + TAFFY_RETURN_CODE_INVALID_POINTS, + // A Percent unit was specified but is not valid in this context + TAFFY_RETURN_CODE_INVALID_PERCENT, + // A MinContent unit was specified but is not valid in this context + TAFFY_RETURN_CODE_INVALID_MIN_CONTENT, + // A MaxContent unit was specified but is not valid in this context + TAFFY_RETURN_CODE_INVALID_MAX_CONTENT, + // A FitContentPx unit was specified but is not valid in this context + TAFFY_RETURN_CODE_INVALID_FIT_CONTENT_PX, + // A FitContentPercent unit was specified but is not valid in this context + TAFFY_RETURN_CODE_INVALID_FIT_CONTENT_PERCENT, + // An Auto unit was specified but is not valid in this context + TAFFY_RETURN_CODE_INVALID_AUTO, + // An Fr unit was specified but is not valid in this context + TAFFY_RETURN_CODE_INVALID_FR, + // A NaN value was specified but is not valid in this context + TAFFY_RETURN_CODE_UNEXPECTED_NA_N, + // A infinite value was specified but is not valid in this context + TAFFY_RETURN_CODE_UNEXPECTED_INFINITY, + // A negative value was specified but is not valid in this context + TAFFY_RETURN_CODE_UNEXPECTED_NEGATIVE, +} TaffyReturnCode; + +typedef enum TaffyUnit { + // A none value (used to unset optional fields) + TAFFY_UNIT_NONE, + // Fixed Length (pixel) value + TAFFY_UNIT_LENGTH, + // Percentage value + TAFFY_UNIT_PERCENT, + // Min-content size + TAFFY_UNIT_MIN_CONTENT, + // Max-content size + TAFFY_UNIT_MAX_CONTENT, + // fit-content() function with a pixel limit + TAFFY_UNIT_FIT_CONTENT_PX, + // fit-content() function with a percentage limit + TAFFY_UNIT_FIT_CONTENT_PERCENT, + // Automatic values + TAFFY_UNIT_AUTO, + // fr unit + TAFFY_UNIT_FR, +} TaffyUnit; + +typedef struct TaffyStyle TaffyStyle; + +typedef struct TaffyTree TaffyTree; + +typedef const struct TaffyStyle *TaffyStyleConstRef; + +typedef struct TaffyStyle *TaffyStyleMutRef; + +typedef struct TaffyDimension { + // The value. If the unit is variant that doesn't require a value (e.g. Auto) then the value is ignored. + float value; + enum TaffyUnit unit; +} TaffyDimension; + +// For all fields, zero represents not set +typedef struct TaffyGridPlacement { + int16_t start; + int16_t end; + uint16_t span; +} TaffyGridPlacement; + +typedef struct TaffyTree *TaffyTreeOwnedRef; + +typedef struct TaffyTree *TaffyTreeMutRef; + +typedef struct TaffyNodeId { + uint64_t _0; +} TaffyNodeId; + +typedef struct TaffyNodeIdResult { + enum TaffyReturnCode return_code; + struct TaffyNodeId value; +} TaffyNodeIdResult; + +typedef struct TaffyStyleMutRefResult { + enum TaffyReturnCode return_code; + TaffyStyleMutRef value; +} TaffyStyleMutRefResult; + +typedef struct TaffySize { + float width; + float height; +} TaffySize; + +typedef struct TaffySize (*TaffyMeasureFunction)(enum TaffyMeasureMode width_measure_mode, + float width, + enum TaffyMeasureMode height_measure_mode, + float height, + void *context); + +typedef struct TaffyLayout { + float x; + float y; + float width; + float height; +} TaffyLayout; + +typedef struct TaffyResult_TaffyLayout { + enum TaffyReturnCode return_code; + struct TaffyLayout value; +} TaffyResult_TaffyLayout; + +typedef const struct TaffyTree *TaffyTreeConstRef; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +enum TaffyDisplay TaffyStyle_GetDisplay(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetDisplay(TaffyStyleMutRef raw_style, enum TaffyDisplay value); + +enum TaffyPosition TaffyStyle_GetPosition(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetPosition(TaffyStyleMutRef raw_style, enum TaffyPosition value); + +enum TaffyOverflow TaffyStyle_GetOverflowX(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetOverflowX(TaffyStyleMutRef raw_style, enum TaffyOverflow value); + +enum TaffyOverflow TaffyStyle_GetOverflowY(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetOverflowY(TaffyStyleMutRef raw_style, enum TaffyOverflow value); + +int32_t TaffyStyle_GetAlignContent(TaffyStyleConstRef raw_style); + +int32_t TaffyStyle_GetAlignItems(TaffyStyleConstRef raw_style); + +int32_t TaffyStyle_GetAlignSelf(TaffyStyleConstRef raw_style); + +int32_t TaffyStyle_GetJustifyContent(TaffyStyleConstRef raw_style); + +int32_t TaffyStyle_GetJustifyItems(TaffyStyleConstRef raw_style); + +int32_t TaffyStyle_GetJustifySelf(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetAlignContent(TaffyStyleMutRef raw_style, enum TaffyAlignContent value); + +enum TaffyReturnCode TaffyStyle_SetAlignItems(TaffyStyleMutRef raw_style, enum TaffyAlignItems value); + +enum TaffyReturnCode TaffyStyle_SetAlignSelf(TaffyStyleMutRef raw_style, enum TaffyAlignItems value); + +enum TaffyReturnCode TaffyStyle_SetJustifyContent(TaffyStyleMutRef raw_style, enum TaffyAlignContent value); + +enum TaffyReturnCode TaffyStyle_SetJustifyItems(TaffyStyleMutRef raw_style, enum TaffyAlignItems value); + +enum TaffyReturnCode TaffyStyle_SetJustifySelf(TaffyStyleMutRef raw_style, enum TaffyAlignItems value); + +enum TaffyFlexDirection TaffyStyle_GetFlexDirection(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetFlexDirection(TaffyStyleMutRef raw_style, enum TaffyFlexDirection value); + +enum TaffyFlexWrap TaffyStyle_GetFlexWrap(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetFlexWrap(TaffyStyleMutRef raw_style, enum TaffyFlexWrap value); + +enum TaffyGridAutoFlow TaffyStyle_GetGridAutoFlow(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetGridAutoFlow(TaffyStyleMutRef raw_style, enum TaffyGridAutoFlow value); + +struct TaffyDimension TaffyStyle_GetWidth(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetWidth(TaffyStyleMutRef raw_style, float value, enum TaffyUnit unit); + +struct TaffyDimension TaffyStyle_GetHeight(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetHeight(TaffyStyleMutRef raw_style, float value, enum TaffyUnit unit); + +struct TaffyDimension TaffyStyle_GetMinWidth(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetMinWidth(TaffyStyleMutRef raw_style, float value, enum TaffyUnit unit); + +struct TaffyDimension TaffyStyle_GetMinHeight(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetMinHeight(TaffyStyleMutRef raw_style, float value, enum TaffyUnit unit); + +struct TaffyDimension TaffyStyle_GetMaxWidth(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetMaxWidth(TaffyStyleMutRef raw_style, float value, enum TaffyUnit unit); + +struct TaffyDimension TaffyStyle_GetMaxHeight(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetMaxHeight(TaffyStyleMutRef raw_style, float value, enum TaffyUnit unit); + +struct TaffyDimension TaffyStyle_GetInsetTop(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetInsetTop(TaffyStyleMutRef raw_style, float value, enum TaffyUnit unit); + +struct TaffyDimension TaffyStyle_GetInsetBottom(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetInsetBottom(TaffyStyleMutRef raw_style, float value, enum TaffyUnit unit); + +struct TaffyDimension TaffyStyle_GetInsetLeft(TaffyStyleConstRef raw_style); + +struct TaffyDimension TaffyStyle_GetInsetRight(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetInsetLeft(TaffyStyleMutRef raw_style, float value, enum TaffyUnit unit); + +enum TaffyReturnCode TaffyStyle_SetInsetRight(TaffyStyleMutRef raw_style, float value, enum TaffyUnit unit); + +struct TaffyDimension TaffyStyle_GetMarginTop(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetMarginTop(TaffyStyleMutRef raw_style, float value, enum TaffyUnit unit); + +struct TaffyDimension TaffyStyle_GetMarginBottom(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetMarginBottom(TaffyStyleMutRef raw_style, float value, enum TaffyUnit unit); + +struct TaffyDimension TaffyStyle_GetMarginLeft(TaffyStyleConstRef raw_style); + +struct TaffyDimension TaffyStyle_GetMarginRight(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetMarginLeft(TaffyStyleMutRef raw_style, float value, enum TaffyUnit unit); + +enum TaffyReturnCode TaffyStyle_SetMarginRight(TaffyStyleMutRef raw_style, float value, enum TaffyUnit unit); + +struct TaffyDimension TaffyStyle_GetPaddingTop(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetPaddingTop(TaffyStyleMutRef raw_style, float value, enum TaffyUnit unit); + +struct TaffyDimension TaffyStyle_GetPaddingBottom(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetPaddingBottom(TaffyStyleMutRef raw_style, float value, enum TaffyUnit unit); + +struct TaffyDimension TaffyStyle_GetPaddingLeft(TaffyStyleConstRef raw_style); + +struct TaffyDimension TaffyStyle_GetPaddingRight(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetPaddingLeft(TaffyStyleMutRef raw_style, float value, enum TaffyUnit unit); + +enum TaffyReturnCode TaffyStyle_SetPaddingRight(TaffyStyleMutRef raw_style, float value, enum TaffyUnit unit); + +struct TaffyDimension TaffyStyle_GetBorderTop(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetBorderTop(TaffyStyleMutRef raw_style, float value, enum TaffyUnit unit); + +struct TaffyDimension TaffyStyle_GetBorderBottom(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetBorderBottom(TaffyStyleMutRef raw_style, float value, enum TaffyUnit unit); + +struct TaffyDimension TaffyStyle_GetBorderLeft(TaffyStyleConstRef raw_style); + +struct TaffyDimension TaffyStyle_GetBorderRight(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetBorderLeft(TaffyStyleMutRef raw_style, float value, enum TaffyUnit unit); + +enum TaffyReturnCode TaffyStyle_SetBorderRight(TaffyStyleMutRef raw_style, float value, enum TaffyUnit unit); + +struct TaffyDimension TaffyStyle_GetColumnGap(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetColumnGap(TaffyStyleMutRef raw_style, float value, enum TaffyUnit unit); + +struct TaffyDimension TaffyStyle_GetRowGap(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetRowGap(TaffyStyleMutRef raw_style, float value, enum TaffyUnit unit); + +float TaffyStyle_GetAspectRatio(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetAspectRatio(TaffyStyleMutRef raw_style, float value); + +float TaffyStyle_GetScrollbarWidth(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetScrollbarWidth(TaffyStyleMutRef raw_style, float value); + +struct TaffyDimension TaffyStyle_GetFlexBasis(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetFlexBasis(TaffyStyleMutRef raw_style, float value, enum TaffyUnit unit); + +float TaffyStyle_GetFlexGrow(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetFlexGrow(TaffyStyleMutRef raw_style, float value); + +float TaffyStyle_GetFlexShrink(TaffyStyleConstRef raw_style); + +enum TaffyReturnCode TaffyStyle_SetFlexShrink(TaffyStyleMutRef raw_style, float value); + +// Function to set all the value of margin +enum TaffyReturnCode TaffyStyle_SetMargin(TaffyStyleMutRef raw_style, enum TaffyEdge edge, struct TaffyDimension value); + +// Get grid item's column placement +struct TaffyGridPlacement TaffyStyle_GetGridColumn(TaffyStyleMutRef raw_style); + +// Set grid item's column placement +enum TaffyReturnCode TaffyStyle_SetGridColumn(TaffyStyleMutRef raw_style, struct TaffyGridPlacement placement); + +// Get grid item's row placement +struct TaffyGridPlacement TaffyStyle_GetGridRow(TaffyStyleMutRef raw_style); + +// Set grid item's row placement +enum TaffyReturnCode TaffyStyle_SetGridRow(TaffyStyleMutRef raw_style, struct TaffyGridPlacement placement); + +// Create a TaffyTree instance +TaffyTreeOwnedRef TaffyTree_New(void); + +// Free a TaffyTree instance +enum TaffyReturnCode TaffyTree_Free(TaffyTreeOwnedRef raw_tree); + +// Create a new Node in the TaffyTree. Returns a NodeId handle to the node. +enum TaffyReturnCode TaffyTree_ComputeLayout(TaffyTreeMutRef raw_tree, + struct TaffyNodeId node_id, + float available_width, + float available_height); + +// Create a new Node in the TaffyTree. Returns a NodeId handle to the node. +enum TaffyReturnCode TaffyTree_PrintTree(TaffyTreeMutRef raw_tree, struct TaffyNodeId node_id); + +// Create a new Node in the TaffyTree. Returns a NodeId handle to the node. +struct TaffyNodeIdResult TaffyTree_NewNode(TaffyTreeMutRef raw_tree); + +// Remove and Free a Node within a TaffyTree +enum TaffyReturnCode TaffyTree_RemoveNode(TaffyTreeMutRef raw_tree, struct TaffyNodeId node_id); + +// Remove and Free a Node within a TaffyTree +enum TaffyReturnCode TaffyTree_AppendChild(TaffyTreeMutRef raw_tree, + struct TaffyNodeId parent_node_id, + struct TaffyNodeId child_node_id); + +// Create a new Node in the TaffyTree. Returns a NodeId handle to the node. +struct TaffyStyleMutRefResult TaffyTree_GetStyleMut(TaffyTreeMutRef raw_tree, struct TaffyNodeId node_id); + +// Create a new Node in the TaffyTree. Returns a NodeId handle to the node. +enum TaffyReturnCode TaffyTree_SetNodeContext(TaffyTreeMutRef raw_tree, + struct TaffyNodeId node_id, + TaffyMeasureFunction measure_function, + void *context); + +// Create a new Node in the TaffyTree. Returns a NodeId handle to the node. +struct TaffyResult_TaffyLayout TaffyTree_GetLayout(TaffyTreeConstRef raw_tree, struct TaffyNodeId node_id); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/bindings/c/rust-toolchain.toml b/bindings/c/rust-toolchain.toml new file mode 100644 index 000000000..271800cb2 --- /dev/null +++ b/bindings/c/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" \ No newline at end of file diff --git a/bindings/c/src/error.rs b/bindings/c/src/error.rs new file mode 100644 index 000000000..238a61617 --- /dev/null +++ b/bindings/c/src/error.rs @@ -0,0 +1,132 @@ +//! Return types for C FFI + +#[macro_export] +macro_rules! ok { + ($value:expr) => { + return TaffyFFIResult::from_value($value); + }; +} + +#[macro_export] +macro_rules! bail { + ($return_code:ident) => { + return TaffyFFIResult::from_return_code(TaffyReturnCode::$return_code); + }; +} + +#[macro_export] +macro_rules! bail_if_null { + ($raw_ptr:expr, $err_code:ident) => { + if $raw_ptr.is_null() { + $crate::bail!($err_code); + } + }; +} + +#[macro_export] +macro_rules! debug_assert_non_null { + ($raw_ptr:expr) => { + #[cfg(debug)] + if $raw_ptr.is_null() { + eprintln!("Supplied pointer was null"); + ::std::process::exit(1); + } + }; +} + +#[macro_export] +macro_rules! try_or { + ($error_code:ident, $block:expr) => { + match { $block } { + Ok(val) => val, + Err(_) => { + bail!($error_code); + } + } + }; +} + +pub(crate) trait TaffyFFIResult { + type Value; + fn from_value(value: Self::Value) -> Self; + fn from_return_code(return_code: TaffyReturnCode) -> Self; +} + +pub(crate) trait TaffyFFIDefault { + fn default() -> Self; +} +impl TaffyFFIDefault for f32 { + fn default() -> Self { + 0.0 + } +} +impl TaffyFFIDefault for i32 { + fn default() -> Self { + 0 + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(C)] +pub enum TaffyReturnCode { + /// Operation suceeded + Ok, + /// The style pointer passed was null + NullStylePointer, + /// The tree pointer passed was null + NullTreePointer, + /// The node referenced by the node id passed does not exist + InvalidNodeId, + /// An enum value was specified that was outside the range of valid value for this enum + InvalidEnumValue, + /// A Points unit was specified but is not valid in this context + InvalidNone, + /// A Points unit was specified but is not valid in this context + InvalidPoints, + /// A Percent unit was specified but is not valid in this context + InvalidPercent, + /// A MinContent unit was specified but is not valid in this context + InvalidMinContent, + /// A MaxContent unit was specified but is not valid in this context + InvalidMaxContent, + /// A FitContentPx unit was specified but is not valid in this context + InvalidFitContentPx, + /// A FitContentPercent unit was specified but is not valid in this context + InvalidFitContentPercent, + /// An Auto unit was specified but is not valid in this context + InvalidAuto, + /// An Fr unit was specified but is not valid in this context + InvalidFr, + /// A NaN value was specified but is not valid in this context + UnexpectedNaN, + /// A infinite value was specified but is not valid in this context + UnexpectedInfinity, + /// A negative value was specified but is not valid in this context + UnexpectedNegative, +} + +impl TaffyFFIResult for TaffyReturnCode { + type Value = TaffyReturnCode; + fn from_value(value: Self::Value) -> Self { + value + } + fn from_return_code(return_code: TaffyReturnCode) -> Self { + return_code + } +} + +#[repr(C)] +pub struct TaffyResult { + pub return_code: TaffyReturnCode, + pub value: T, +} + +impl TaffyFFIResult for TaffyResult { + type Value = T; + fn from_value(value: Self::Value) -> Self { + Self { return_code: TaffyReturnCode::Ok, value } + } + fn from_return_code(return_code: TaffyReturnCode) -> Self { + Self { return_code, value: T::default() } + } +} diff --git a/bindings/c/src/lib.rs b/bindings/c/src/lib.rs new file mode 100644 index 000000000..54a01e42b --- /dev/null +++ b/bindings/c/src/lib.rs @@ -0,0 +1,26 @@ +mod error; +mod style; +mod style_enums; +mod tree; +mod value; + +pub struct TaffyStyle; +pub type TaffyStyleMutRef = *mut TaffyStyle; +pub type TaffyStyleConstRef = *const TaffyStyle; + +impl TaffyFFIDefault for TaffyStyleMutRef { + fn default() -> Self { + core::ptr::null_mut() + } +} +impl TaffyFFIDefault for TaffyStyleConstRef { + fn default() -> Self { + core::ptr::null() + } +} + +pub use error::*; +pub use style::*; +pub use style_enums::*; +pub use tree::*; +pub use value::*; diff --git a/bindings/c/src/style.rs b/bindings/c/src/style.rs new file mode 100644 index 000000000..4a68924cc --- /dev/null +++ b/bindings/c/src/style.rs @@ -0,0 +1,334 @@ +//! Public API for C FFI + +use super::{ + debug_assert_non_null, TaffyAlignContent, TaffyAlignItems, TaffyDimension, TaffyDisplay, TaffyEdge, + TaffyFlexDirection, TaffyFlexWrap, TaffyGridAutoFlow, TaffyGridPlacement, TaffyOverflow, TaffyPosition, + TaffyReturnCode, TaffyStyleConstRef, TaffyStyleMutRef, TaffyUnit, +}; +use taffy::prelude as core; + +/// Assert that the passed raw style pointer is non-null +/// Then give the passed expression access to the value of the inner [`core::Style`] struct pointed to by the raw style pointer +/// Return whatever the expression evaluates to wrapped in a [`TaffyDimensionResult`] if the expression does not interally return. +macro_rules! get_style { + ($raw_style_ptr:expr, $style_ident:ident, $block:expr) => {{ + debug_assert_non_null!($raw_style_ptr); + let $style_ident = unsafe { &*($raw_style_ptr as *const core::Style) }; + + let return_value = $block; + + return_value.into() + }}; +} + +/// Assert that the passed raw style pointer is non-null +/// Then give the passed expression mutable access to the value of the inner [`core::Style`] struct pointed to by the raw style pointer +/// Return [`TaffyReturnCode::Ok`] if the expression does not internally return. +macro_rules! with_style_mut { + ($raw_style_ptr:expr, $style_ident:ident, $block:expr) => {{ + debug_assert_non_null!($raw_style_ptr); + let $style_ident = unsafe { &mut *($raw_style_ptr as *mut core::Style) }; + + $block; + + TaffyReturnCode::Ok + }}; +} + +/// Attempt to convert a [`TaffyDimension`] into a type that implements `TryFrom` +/// In the case of a conversion error, return a [`TaffyReturnCode`]. +macro_rules! try_from_value { + ($value:expr) => { + match $value.try_into() { + Ok(val) => val, + Err(err) => return err, + } + }; +} + +/// Attempt to convert a [`TaffyUnit`] and a `f32` into a type that implements `TryFrom` +/// In the case of a conversion error, return a [`TaffyReturnCode`]. +macro_rules! try_from_raw { + ($unit:expr, $value:expr) => { + try_from_value!(TaffyDimension::from_raw($unit, $value)) + }; +} + +// Simple enum properties + +macro_rules! enum_prop_getter { + ($func_name:ident; $enum:ident; $($props:ident).+) => { + #[no_mangle] + #[allow(clippy::missing_safety_doc)] + pub unsafe extern "C" fn $func_name(raw_style: TaffyStyleConstRef) -> $enum { + get_style!(raw_style, style, style.$($props).*) + } + }; +} + +macro_rules! option_enum_prop_getter { + ($func_name:ident; $($props:ident).+) => { + #[no_mangle] + #[allow(clippy::missing_safety_doc)] + pub unsafe extern "C" fn $func_name(raw_style: TaffyStyleConstRef) -> i32 { + get_style!(raw_style, style, style.$($props).*.map(|v| v as i32).unwrap_or(0)) + } + }; +} + +// Generate a function to set a style value such as margin.top or size.width +macro_rules! enum_prop_setter { + ($func_name:ident; $enum:ident; $($props:ident).+) => { + #[no_mangle] + #[allow(clippy::missing_safety_doc)] + pub unsafe extern "C" fn $func_name(raw_style: TaffyStyleMutRef, value: $enum) -> TaffyReturnCode { + with_style_mut!(raw_style, style, style.$($props).* = value.into()) + } + }; +} + +// Generate a function to get a style value such as margin.top or size.width +macro_rules! style_value_prop_getter { + ($func_name:ident; $($props:ident).+) => { + #[no_mangle] + #[allow(clippy::missing_safety_doc)] + pub unsafe extern "C" fn $func_name(raw_style: TaffyStyleConstRef) -> TaffyDimension { + get_style!(raw_style, style, style.$($props).*) + } + }; +} + +// Generate a function to set a style value such as margin.top or size.width +macro_rules! style_value_prop_setter { + ($func_name:ident; $($props:ident).+) => { + #[no_mangle] + #[allow(clippy::missing_safety_doc)] + pub unsafe extern "C" fn $func_name(raw_style: TaffyStyleMutRef, value: f32, unit: TaffyUnit) -> TaffyReturnCode { + with_style_mut!(raw_style, style, style.$($props).* = try_from_raw!(unit, value)) + } + }; +} + +// Generate a function to get a style value such as margin.top or size.width +macro_rules! float_prop_getter { + ($func_name:ident; $($props:ident).+) => { + #[no_mangle] + #[allow(clippy::missing_safety_doc)] + pub unsafe extern "C" fn $func_name(raw_style: TaffyStyleConstRef) -> f32 { + get_style!(raw_style, style, style.$($props).*) + } + }; +} + +// Generate a function to set a style value such as margin.top or size.width +macro_rules! float_prop_setter { + ($func_name:ident; $($props:ident).+) => { + #[no_mangle] + #[allow(clippy::missing_safety_doc)] + pub unsafe extern "C" fn $func_name(raw_style: TaffyStyleMutRef, value: f32) -> TaffyReturnCode { + with_style_mut!(raw_style, style, style.$($props).* = value) + } + }; +} + +enum_prop_getter!(TaffyStyle_GetDisplay; TaffyDisplay; display); +enum_prop_setter!(TaffyStyle_SetDisplay; TaffyDisplay; display); + +// Position +enum_prop_getter!(TaffyStyle_GetPosition; TaffyPosition; position); +enum_prop_setter!(TaffyStyle_SetPosition; TaffyPosition; position); + +// Overflow +enum_prop_getter!(TaffyStyle_GetOverflowX; TaffyOverflow; overflow.x); +enum_prop_setter!(TaffyStyle_SetOverflowX; TaffyOverflow; overflow.x); +enum_prop_getter!(TaffyStyle_GetOverflowY; TaffyOverflow; overflow.y); +enum_prop_setter!(TaffyStyle_SetOverflowY; TaffyOverflow; overflow.y); + +// Alignment +option_enum_prop_getter!(TaffyStyle_GetAlignContent; align_content); +option_enum_prop_getter!(TaffyStyle_GetAlignItems; align_items); +option_enum_prop_getter!(TaffyStyle_GetAlignSelf; align_self); +option_enum_prop_getter!(TaffyStyle_GetJustifyContent; justify_content); +option_enum_prop_getter!(TaffyStyle_GetJustifyItems; justify_items); +option_enum_prop_getter!(TaffyStyle_GetJustifySelf; justify_self); +enum_prop_setter!(TaffyStyle_SetAlignContent; TaffyAlignContent; align_content); +enum_prop_setter!(TaffyStyle_SetAlignItems; TaffyAlignItems; align_items); +enum_prop_setter!(TaffyStyle_SetAlignSelf; TaffyAlignItems; align_self); +enum_prop_setter!(TaffyStyle_SetJustifyContent; TaffyAlignContent; justify_content); +enum_prop_setter!(TaffyStyle_SetJustifyItems; TaffyAlignItems; justify_items); +enum_prop_setter!(TaffyStyle_SetJustifySelf; TaffyAlignItems; justify_self); + +// FlexDirection & FlexWrap +enum_prop_getter!(TaffyStyle_GetFlexDirection; TaffyFlexDirection; flex_direction); +enum_prop_setter!(TaffyStyle_SetFlexDirection; TaffyFlexDirection; flex_direction); +enum_prop_getter!(TaffyStyle_GetFlexWrap; TaffyFlexWrap; flex_wrap); +enum_prop_setter!(TaffyStyle_SetFlexWrap; TaffyFlexWrap; flex_wrap); + +// GridAutoFlow +enum_prop_getter!(TaffyStyle_GetGridAutoFlow; TaffyGridAutoFlow; grid_auto_flow); +enum_prop_setter!(TaffyStyle_SetGridAutoFlow; TaffyGridAutoFlow; grid_auto_flow); + +/* API variant with single parameter that combines "value" and "unit" into a `TaffyDimension` struct */ + +// Size +style_value_prop_getter!(TaffyStyle_GetWidth; size.width); +style_value_prop_setter!(TaffyStyle_SetWidth; size.width); +style_value_prop_getter!(TaffyStyle_GetHeight; size.height); +style_value_prop_setter!(TaffyStyle_SetHeight; size.height); + +// MinSize +style_value_prop_getter!(TaffyStyle_GetMinWidth; min_size.width); +style_value_prop_setter!(TaffyStyle_SetMinWidth; min_size.width); +style_value_prop_getter!(TaffyStyle_GetMinHeight; min_size.height); +style_value_prop_setter!(TaffyStyle_SetMinHeight; min_size.height); + +// MaxSize +style_value_prop_getter!(TaffyStyle_GetMaxWidth; max_size.width); +style_value_prop_setter!(TaffyStyle_SetMaxWidth; max_size.width); +style_value_prop_getter!(TaffyStyle_GetMaxHeight; max_size.height); +style_value_prop_setter!(TaffyStyle_SetMaxHeight; max_size.height); + +// Inset +style_value_prop_getter!(TaffyStyle_GetInsetTop; inset.top); +style_value_prop_setter!(TaffyStyle_SetInsetTop; inset.top); +style_value_prop_getter!(TaffyStyle_GetInsetBottom; inset.bottom); +style_value_prop_setter!(TaffyStyle_SetInsetBottom; inset.bottom); +style_value_prop_getter!(TaffyStyle_GetInsetLeft; inset.left); +style_value_prop_getter!(TaffyStyle_GetInsetRight; inset.right); +style_value_prop_setter!(TaffyStyle_SetInsetLeft; inset.left); +style_value_prop_setter!(TaffyStyle_SetInsetRight; inset.right); + +// Margin +style_value_prop_getter!(TaffyStyle_GetMarginTop; margin.top); +style_value_prop_setter!(TaffyStyle_SetMarginTop; margin.top); +style_value_prop_getter!(TaffyStyle_GetMarginBottom; margin.bottom); +style_value_prop_setter!(TaffyStyle_SetMarginBottom; margin.bottom); +style_value_prop_getter!(TaffyStyle_GetMarginLeft; margin.left); +style_value_prop_getter!(TaffyStyle_GetMarginRight; margin.right); +style_value_prop_setter!(TaffyStyle_SetMarginLeft; margin.left); +style_value_prop_setter!(TaffyStyle_SetMarginRight; margin.right); + +// Padding +style_value_prop_getter!(TaffyStyle_GetPaddingTop; padding.top); +style_value_prop_setter!(TaffyStyle_SetPaddingTop; padding.top); +style_value_prop_getter!(TaffyStyle_GetPaddingBottom; padding.bottom); +style_value_prop_setter!(TaffyStyle_SetPaddingBottom; padding.bottom); +style_value_prop_getter!(TaffyStyle_GetPaddingLeft; padding.left); +style_value_prop_getter!(TaffyStyle_GetPaddingRight; padding.right); +style_value_prop_setter!(TaffyStyle_SetPaddingLeft; padding.left); +style_value_prop_setter!(TaffyStyle_SetPaddingRight; padding.right); + +// Border +style_value_prop_getter!(TaffyStyle_GetBorderTop; border.top); +style_value_prop_setter!(TaffyStyle_SetBorderTop; border.top); +style_value_prop_getter!(TaffyStyle_GetBorderBottom; border.bottom); +style_value_prop_setter!(TaffyStyle_SetBorderBottom; border.bottom); +style_value_prop_getter!(TaffyStyle_GetBorderLeft; border.left); +style_value_prop_getter!(TaffyStyle_GetBorderRight; border.right); +style_value_prop_setter!(TaffyStyle_SetBorderLeft; border.left); +style_value_prop_setter!(TaffyStyle_SetBorderRight; border.right); + +// Gap +style_value_prop_getter!(TaffyStyle_GetColumnGap; gap.width); +style_value_prop_setter!(TaffyStyle_SetColumnGap; gap.width); +style_value_prop_getter!(TaffyStyle_GetRowGap; gap.height); +style_value_prop_setter!(TaffyStyle_SetRowGap; gap.height); + +// Aspect ratio +#[no_mangle] +#[allow(clippy::missing_safety_doc)] +pub unsafe extern "C" fn TaffyStyle_GetAspectRatio(raw_style: TaffyStyleConstRef) -> f32 { + get_style!(raw_style, style, style.aspect_ratio.unwrap_or(f32::NAN)) +} +#[no_mangle] +#[allow(clippy::missing_safety_doc)] +pub unsafe extern "C" fn TaffyStyle_SetAspectRatio(raw_style: TaffyStyleMutRef, value: f32) -> TaffyReturnCode { + with_style_mut!(raw_style, style, { + if value.is_finite() && value > 0.0 { + style.aspect_ratio = Some(value) + } else { + style.aspect_ratio = None; + } + }) +} + +// Scrollbar width +float_prop_getter!(TaffyStyle_GetScrollbarWidth; scrollbar_width); +float_prop_setter!(TaffyStyle_SetScrollbarWidth; scrollbar_width); + +// Flex +style_value_prop_getter!(TaffyStyle_GetFlexBasis; flex_basis); +style_value_prop_setter!(TaffyStyle_SetFlexBasis; flex_basis); +float_prop_getter!(TaffyStyle_GetFlexGrow; flex_grow); +float_prop_setter!(TaffyStyle_SetFlexGrow; flex_grow); +float_prop_getter!(TaffyStyle_GetFlexShrink; flex_shrink); +float_prop_setter!(TaffyStyle_SetFlexShrink; flex_shrink); + +/// Function to set all the value of margin +#[no_mangle] +#[allow(clippy::missing_safety_doc)] +pub unsafe extern "C" fn TaffyStyle_SetMargin( + raw_style: TaffyStyleMutRef, + edge: TaffyEdge, + value: TaffyDimension, +) -> TaffyReturnCode { + let value = try_from_value!(value); + with_style_mut!(raw_style, style, { + match edge { + TaffyEdge::Top => style.margin.top = value, + TaffyEdge::Bottom => style.margin.bottom = value, + TaffyEdge::Left => style.margin.left = value, + TaffyEdge::Right => style.margin.right = value, + TaffyEdge::Vertical => { + style.margin.top = value; + style.margin.bottom = value; + } + TaffyEdge::Horizontal => { + style.margin.left = value; + style.margin.right = value; + } + TaffyEdge::All => { + style.margin.top = value; + style.margin.bottom = value; + style.margin.left = value; + style.margin.right = value; + } + }; + }) +} + +/* Grid APIs */ + +/// Get grid item's column placement +#[no_mangle] +#[allow(clippy::missing_safety_doc)] +pub unsafe extern "C" fn TaffyStyle_GetGridColumn(raw_style: TaffyStyleMutRef) -> TaffyGridPlacement { + get_style!(raw_style, style, style.grid_column) +} + +/// Set grid item's column placement +#[no_mangle] +#[allow(clippy::missing_safety_doc)] +pub unsafe extern "C" fn TaffyStyle_SetGridColumn( + raw_style: TaffyStyleMutRef, + placement: TaffyGridPlacement, +) -> TaffyReturnCode { + with_style_mut!(raw_style, style, style.grid_column = placement.into()) +} + +/// Get grid item's row placement +#[no_mangle] +#[allow(clippy::missing_safety_doc)] +pub unsafe extern "C" fn TaffyStyle_GetGridRow(raw_style: TaffyStyleMutRef) -> TaffyGridPlacement { + get_style!(raw_style, style, style.grid_row) +} + +/// Set grid item's row placement +#[no_mangle] +#[allow(clippy::missing_safety_doc)] +pub unsafe extern "C" fn TaffyStyle_SetGridRow( + raw_style: TaffyStyleMutRef, + placement: TaffyGridPlacement, +) -> TaffyReturnCode { + with_style_mut!(raw_style, style, style.grid_row = placement.into()) +} diff --git a/bindings/c/src/style_enums.rs b/bindings/c/src/style_enums.rs new file mode 100644 index 000000000..479bfb95d --- /dev/null +++ b/bindings/c/src/style_enums.rs @@ -0,0 +1,384 @@ +use taffy::prelude as core; + +/// Sets the layout used for the children of this node +/// +/// The default values depends on on which feature flags are enabled. The order of precedence is: Flex, Grid, Block, None. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[repr(C)] +pub enum TaffyDisplay { + /// The children will follow the block layout algorithm + Block, + /// The children will follow the flexbox layout algorithm + Flex, + /// The children will follow the CSS Grid layout algorithm + Grid, + /// The children will not be laid out, and will follow absolute positioning + None, +} +impl From for core::Display { + fn from(input: TaffyDisplay) -> core::Display { + match input { + TaffyDisplay::Block => core::Display::Block, + TaffyDisplay::Flex => core::Display::Flex, + TaffyDisplay::Grid => core::Display::Grid, + TaffyDisplay::None => core::Display::None, + } + } +} +impl From for TaffyDisplay { + fn from(input: core::Display) -> TaffyDisplay { + match input { + core::Display::Block => TaffyDisplay::Block, + core::Display::Flex => TaffyDisplay::Flex, + core::Display::Grid => TaffyDisplay::Grid, + core::Display::None => TaffyDisplay::None, + } + } +} + +/// The positioning strategy for this item. +/// +/// This controls both how the origin is determined for the [`Style::position`] field, +/// and whether or not the item will be controlled by flexbox's layout algorithm. +/// +/// WARNING: this enum follows the behavior of [CSS's `position` property](https://developer.mozilla.org/en-US/docs/Web/CSS/position), +/// which can be unintuitive. +/// +/// [`Position::Relative`] is the default value, in contrast to the default behavior in CSS. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[repr(C)] +pub enum TaffyPosition { + /// The offset is computed relative to the final position given by the layout algorithm. + /// Offsets do not affect the position of any other items; they are effectively a correction factor applied at the end. + Relative, + /// The offset is computed relative to this item's closest positioned ancestor, if any. + /// Otherwise, it is placed relative to the origin. + /// No space is created for the item in the page layout, and its size will not be altered. + /// + /// WARNING: to opt-out of layouting entirely, you must use [`Display::None`] instead on your [`Style`] object. + Absolute, +} +impl From for core::Position { + fn from(input: TaffyPosition) -> core::Position { + match input { + TaffyPosition::Relative => core::Position::Relative, + TaffyPosition::Absolute => core::Position::Absolute, + } + } +} +impl From for TaffyPosition { + fn from(input: core::Position) -> TaffyPosition { + match input { + core::Position::Relative => TaffyPosition::Relative, + core::Position::Absolute => TaffyPosition::Absolute, + } + } +} + +/// How children overflowing their container should affect layout +/// +/// In CSS the primary effect of this property is to control whether contents of a parent container that overflow that container should +/// be displayed anyway, be clipped, or trigger the container to become a scroll container. However it also has secondary effects on layout, +/// the main ones being: +/// +/// - The automatic minimum size Flexbox/CSS Grid items with non-`Visible` overflow is `0` rather than being content based +/// - `Overflow::Scroll` nodes have space in the layout reserved for a scrollbar (width controlled by the `scrollbar_width` property) +/// +/// In Taffy, we only implement the layout related secondary effects as we are not concerned with drawing/painting. The amount of space reserved for +/// a scrollbar is controlled by the `scrollbar_width` property. If this is `0` then `Scroll` behaves identically to `Hidden`. +/// +/// +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[repr(C)] +pub enum TaffyOverflow { + /// The automatic minimum size of this node as a flexbox/grid item should be based on the size of it's content. + Visible, + /// The automatic minimum size of this node as a flexbox/grid item should be based on the size of its content. + /// Content that overflows this node should *not* contribute to the scroll region of its parent. + Clip, + /// The automatic minimum size of this node as a flexbox/grid item should be `0`. + Hidden, + /// The automatic minimum size of this node as a flexbox/grid item should be `0`. Additionally, space should be reserved + /// for a scrollbar. The amount of space reserved is controlled by the `scrollbar_width` property. + Scroll, +} +impl From for core::Overflow { + fn from(input: TaffyOverflow) -> core::Overflow { + match input { + TaffyOverflow::Visible => core::Overflow::Visible, + TaffyOverflow::Clip => core::Overflow::Clip, + TaffyOverflow::Hidden => core::Overflow::Hidden, + TaffyOverflow::Scroll => core::Overflow::Scroll, + } + } +} +impl From for TaffyOverflow { + fn from(input: core::Overflow) -> TaffyOverflow { + match input { + core::Overflow::Visible => TaffyOverflow::Visible, + core::Overflow::Clip => TaffyOverflow::Clip, + core::Overflow::Hidden => TaffyOverflow::Hidden, + core::Overflow::Scroll => TaffyOverflow::Scroll, + } + } +} + +/// Used to control how child nodes are aligned. +/// For Flexbox it controls alignment in the cross axis +/// For Grid it controls alignment in the block axis +/// +/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/align-items) +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[repr(C)] +pub enum TaffyAlignItems { + /// Items are aligned according to their algorithm-specific default value + /// This is equivalent to not setting a value in CSS + Normal, + /// Items are packed toward the start of the axis + Start, + /// Items are packed toward the end of the axis + End, + /// Items are packed towards the flex-relative start of the axis. + /// + /// For flex containers with flex_direction RowReverse or ColumnReverse this is equivalent + /// to End. In all other cases it is equivalent to Start. + FlexStart, + /// Items are packed towards the flex-relative end of the axis. + /// + /// For flex containers with flex_direction RowReverse or ColumnReverse this is equivalent + /// to Start. In all other cases it is equivalent to End. + FlexEnd, + /// Items are packed along the center of the cross axis + Center, + /// Items are aligned such as their baselines align + Baseline, + /// Stretch to fill the container + Stretch, +} +impl From for Option { + fn from(input: TaffyAlignItems) -> Option { + match input { + TaffyAlignItems::Normal => None, + TaffyAlignItems::Start => Some(core::AlignItems::Start), + TaffyAlignItems::End => Some(core::AlignItems::End), + TaffyAlignItems::FlexStart => Some(core::AlignItems::FlexStart), + TaffyAlignItems::FlexEnd => Some(core::AlignItems::FlexEnd), + TaffyAlignItems::Center => Some(core::AlignItems::Center), + TaffyAlignItems::Baseline => Some(core::AlignItems::Baseline), + TaffyAlignItems::Stretch => Some(core::AlignItems::Stretch), + } + } +} +impl From> for TaffyAlignItems { + fn from(input: Option) -> TaffyAlignItems { + match input { + None => TaffyAlignItems::Normal, + Some(core::AlignItems::Start) => TaffyAlignItems::Start, + Some(core::AlignItems::End) => TaffyAlignItems::End, + Some(core::AlignItems::FlexStart) => TaffyAlignItems::FlexStart, + Some(core::AlignItems::FlexEnd) => TaffyAlignItems::FlexEnd, + Some(core::AlignItems::Center) => TaffyAlignItems::Center, + Some(core::AlignItems::Baseline) => TaffyAlignItems::Baseline, + Some(core::AlignItems::Stretch) => TaffyAlignItems::Stretch, + } + } +} + +/// Sets the distribution of space between and around content items +/// For Flexbox it controls alignment in the cross axis +/// For Grid it controls alignment in the block axis +/// +/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/align-content) +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[repr(C)] +pub enum TaffyAlignContent { + /// Items are aligned according to their algorithm-specific default value + /// This is equivalent to not setting a value in CSS + Normal, + /// Items are packed toward the start of the axis + Start, + /// Items are packed toward the end of the axis + End, + /// Items are packed towards the flex-relative start of the axis. + /// + /// For flex containers with flex_direction RowReverse or ColumnReverse this is equivalent + /// to End. In all other cases it is equivalent to Start. + FlexStart, + /// Items are packed towards the flex-relative end of the axis. + /// + /// For flex containers with flex_direction RowReverse or ColumnReverse this is equivalent + /// to Start. In all other cases it is equivalent to End. + FlexEnd, + /// Items are centered around the middle of the axis + Center, + /// Items are stretched to fill the container + Stretch, + /// The first and last items are aligned flush with the edges of the container (no gap) + /// The gap between items is distributed evenly. + SpaceBetween, + /// The gap between the first and last items is exactly THE SAME as the gap between items. + /// The gaps are distributed evenly + SpaceEvenly, + /// The gap between the first and last items is exactly HALF the gap between items. + /// The gaps are distributed evenly in proportion to these ratios. + SpaceAround, +} +impl From for Option { + fn from(input: TaffyAlignContent) -> Option { + match input { + TaffyAlignContent::Normal => None, + TaffyAlignContent::Start => Some(core::AlignContent::Start), + TaffyAlignContent::End => Some(core::AlignContent::End), + TaffyAlignContent::FlexStart => Some(core::AlignContent::FlexStart), + TaffyAlignContent::FlexEnd => Some(core::AlignContent::FlexEnd), + TaffyAlignContent::Center => Some(core::AlignContent::Center), + TaffyAlignContent::Stretch => Some(core::AlignContent::Stretch), + TaffyAlignContent::SpaceBetween => Some(core::AlignContent::SpaceBetween), + TaffyAlignContent::SpaceEvenly => Some(core::AlignContent::SpaceEvenly), + TaffyAlignContent::SpaceAround => Some(core::AlignContent::SpaceAround), + } + } +} +impl From> for TaffyAlignContent { + fn from(input: Option) -> TaffyAlignContent { + match input { + None => TaffyAlignContent::Normal, + Some(core::AlignContent::Start) => TaffyAlignContent::Start, + Some(core::AlignContent::End) => TaffyAlignContent::End, + Some(core::AlignContent::FlexStart) => TaffyAlignContent::FlexStart, + Some(core::AlignContent::FlexEnd) => TaffyAlignContent::FlexEnd, + Some(core::AlignContent::Center) => TaffyAlignContent::Center, + Some(core::AlignContent::Stretch) => TaffyAlignContent::Stretch, + Some(core::AlignContent::SpaceBetween) => TaffyAlignContent::SpaceBetween, + Some(core::AlignContent::SpaceAround) => TaffyAlignContent::SpaceAround, + Some(core::AlignContent::SpaceEvenly) => TaffyAlignContent::SpaceEvenly, + } + } +} + +/// Controls whether flex items are forced onto one line or can wrap onto multiple lines. +/// +/// Defaults to [`FlexWrap::NoWrap`] +/// +/// [Specification](https://www.w3.org/TR/css-flexbox-1/#flex-wrap-property) +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[repr(C)] +pub enum TaffyFlexWrap { + /// Items will not wrap and stay on a single line + NoWrap, + /// Items will wrap according to this item's [`FlexDirection`] + Wrap, + /// Items will wrap in the opposite direction to this item's [`FlexDirection`] + WrapReverse, +} +impl From for core::FlexWrap { + fn from(input: TaffyFlexWrap) -> core::FlexWrap { + match input { + TaffyFlexWrap::NoWrap => core::FlexWrap::NoWrap, + TaffyFlexWrap::Wrap => core::FlexWrap::Wrap, + TaffyFlexWrap::WrapReverse => core::FlexWrap::WrapReverse, + } + } +} +impl From for TaffyFlexWrap { + fn from(input: core::FlexWrap) -> TaffyFlexWrap { + match input { + core::FlexWrap::NoWrap => TaffyFlexWrap::NoWrap, + core::FlexWrap::Wrap => TaffyFlexWrap::Wrap, + core::FlexWrap::WrapReverse => TaffyFlexWrap::WrapReverse, + } + } +} + +/// The direction of the flexbox layout main axis. +/// +/// There are always two perpendicular layout axes: main (or primary) and cross (or secondary). +/// Adding items will cause them to be positioned adjacent to each other along the main axis. +/// By varying this value throughout your tree, you can create complex axis-aligned layouts. +/// +/// Items are always aligned relative to the cross axis, and justified relative to the main axis. +/// +/// The default behavior is [`FlexDirection::Row`]. +/// +/// [Specification](https://www.w3.org/TR/css-flexbox-1/#flex-direction-property) +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[repr(C)] +pub enum TaffyFlexDirection { + /// Defines +x as the main axis + /// + /// Items will be added from left to right in a row. + Row, + /// Defines +y as the main axis + /// + /// Items will be added from top to bottom in a column. + Column, + /// Defines -x as the main axis + /// + /// Items will be added from right to left in a row. + RowReverse, + /// Defines -y as the main axis + /// + /// Items will be added from bottom to top in a column. + ColumnReverse, +} +impl From for core::FlexDirection { + fn from(input: TaffyFlexDirection) -> core::FlexDirection { + match input { + TaffyFlexDirection::Row => core::FlexDirection::Row, + TaffyFlexDirection::Column => core::FlexDirection::Column, + TaffyFlexDirection::RowReverse => core::FlexDirection::RowReverse, + TaffyFlexDirection::ColumnReverse => core::FlexDirection::ColumnReverse, + } + } +} +impl From for TaffyFlexDirection { + fn from(input: core::FlexDirection) -> TaffyFlexDirection { + match input { + core::FlexDirection::Row => TaffyFlexDirection::Row, + core::FlexDirection::Column => TaffyFlexDirection::Column, + core::FlexDirection::RowReverse => TaffyFlexDirection::RowReverse, + core::FlexDirection::ColumnReverse => TaffyFlexDirection::ColumnReverse, + } + } +} + +/// Controls whether grid items are placed row-wise or column-wise. And whether the sparse or dense packing algorithm is used. +/// +/// The "dense" packing algorithm attempts to fill in holes earlier in the grid, if smaller items come up later. +/// This may cause items to appear out-of-order, when doing so would fill in holes left by larger items. +/// +/// Defaults to [`GridAutoFlow::Row`] +/// +/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow) +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[repr(C)] +pub enum TaffyGridAutoFlow { + /// Items are placed by filling each row in turn, adding new rows as necessary + Row, + /// Items are placed by filling each column in turn, adding new columns as necessary. + Column, + /// Combines `Row` with the dense packing algorithm. + RowDense, + /// Combines `Column` with the dense packing algorithm. + ColumnDense, +} +impl From for core::GridAutoFlow { + fn from(input: TaffyGridAutoFlow) -> core::GridAutoFlow { + match input { + TaffyGridAutoFlow::Row => core::GridAutoFlow::Row, + TaffyGridAutoFlow::Column => core::GridAutoFlow::Column, + TaffyGridAutoFlow::RowDense => core::GridAutoFlow::RowDense, + TaffyGridAutoFlow::ColumnDense => core::GridAutoFlow::ColumnDense, + } + } +} +impl From for TaffyGridAutoFlow { + fn from(input: core::GridAutoFlow) -> TaffyGridAutoFlow { + match input { + core::GridAutoFlow::Row => TaffyGridAutoFlow::Row, + core::GridAutoFlow::Column => TaffyGridAutoFlow::Column, + core::GridAutoFlow::RowDense => TaffyGridAutoFlow::RowDense, + core::GridAutoFlow::ColumnDense => TaffyGridAutoFlow::ColumnDense, + } + } +} diff --git a/bindings/c/src/tree.rs b/bindings/c/src/tree.rs new file mode 100644 index 000000000..69f70fc3c --- /dev/null +++ b/bindings/c/src/tree.rs @@ -0,0 +1,244 @@ +use super::{ + bail, bail_if_null, ok, try_or, TaffyFFIDefault, TaffyFFIResult, TaffyLayout, TaffyMeasureMode, TaffyResult, + TaffyReturnCode, TaffySize, TaffyStyleMutRef, +}; +use ::core::ffi::c_void; +use taffy::prelude as core; +use taffy::style::AvailableSpace; +use taffy::TaffyTree as CoreTaffy; + +pub type TaffyMeasureFunction = extern "C" fn( + width_measure_mode: TaffyMeasureMode, + width: f32, + height_measure_mode: TaffyMeasureMode, + height: f32, + context: *mut c_void, +) -> TaffySize; + +#[allow(dead_code)] // false positive +struct NodeContext { + context: *mut c_void, + measure_function: TaffyMeasureFunction, +} + +pub struct TaffyTree { + inner: CoreTaffy, +} +pub type TaffyTreeOwnedRef = *mut TaffyTree; +pub type TaffyTreeMutRef = *mut TaffyTree; +pub type TaffyTreeConstRef = *const TaffyTree; + +#[repr(C)] +pub struct TaffyNodeId(u64); +impl TaffyFFIDefault for TaffyNodeId { + fn default() -> Self { + Self(0) + } +} +impl From for TaffyNodeId { + fn from(input: core::NodeId) -> Self { + TaffyNodeId(input.into()) + } +} +impl From for core::NodeId { + fn from(input: TaffyNodeId) -> Self { + core::NodeId::new(input.0) + } +} + +macro_rules! with_tree { + ($raw_tree_ptr:expr, $tree_ident:ident, $block:expr) => {{ + bail_if_null!($raw_tree_ptr, NullTreePointer); + let $tree_ident = unsafe { &*($raw_tree_ptr as *const TaffyTree) }; + $block + }}; +} + +macro_rules! with_tree_mut { + ($raw_tree_ptr:expr, $tree_ident:ident, $block:expr) => {{ + bail_if_null!($raw_tree_ptr, NullTreePointer); + let $tree_ident = unsafe { &mut *($raw_tree_ptr as *mut TaffyTree) }; + $block + }}; +} + +fn available_space_from_f32(input: f32) -> core::AvailableSpace { + if input.is_finite() && input >= 0.0 { + core::AvailableSpace::Definite(input) + } else if input == f32::NEG_INFINITY { + core::AvailableSpace::MinContent + } else { + core::AvailableSpace::MaxContent + } +} + +// ------------------------------------------------- +// Create and Free +// ------------------------------------------------- + +/// Create a TaffyTree instance +#[no_mangle] +#[allow(clippy::missing_safety_doc)] +pub unsafe extern "C" fn TaffyTree_New() -> TaffyTreeOwnedRef { + Box::into_raw(Box::new(TaffyTree { inner: CoreTaffy::new() })) +} + +/// Free a TaffyTree instance +#[no_mangle] +#[allow(clippy::missing_safety_doc)] +pub unsafe extern "C" fn TaffyTree_Free(raw_tree: TaffyTreeOwnedRef) -> TaffyReturnCode { + bail_if_null!(raw_tree, NullTreePointer); + drop(Box::from_raw(raw_tree)); + TaffyReturnCode::Ok +} + +// ------------------------------------------------- +// Compute and Print +// ------------------------------------------------- + +/// Create a new Node in the TaffyTree. Returns a NodeId handle to the node. +#[no_mangle] +#[allow(clippy::missing_safety_doc)] +pub unsafe extern "C" fn TaffyTree_ComputeLayout( + raw_tree: TaffyTreeMutRef, + node_id: TaffyNodeId, + available_width: f32, + available_height: f32, +) -> TaffyReturnCode { + with_tree_mut!(raw_tree, tree, { + let available_space = core::Size { + width: available_space_from_f32(available_width), + height: available_space_from_f32(available_height), + }; + try_or!( + InvalidNodeId, + tree.inner.compute_layout_with_measure( + node_id.into(), + available_space, + |known_dimensions, available_space, _node_id, node_context| { + let (width, width_measure_mode) = match (known_dimensions.width, available_space.width) { + (Some(width), _) => (width, TaffyMeasureMode::Exact), + (None, AvailableSpace::Definite(width)) => (width, TaffyMeasureMode::FitContent), + (None, AvailableSpace::MaxContent) => (f32::INFINITY, TaffyMeasureMode::MaxContent), + (None, AvailableSpace::MinContent) => (f32::INFINITY, TaffyMeasureMode::MinContent), + }; + let (height, height_measure_mode) = match (known_dimensions.height, available_space.height) { + (Some(height), _) => (height, TaffyMeasureMode::Exact), + (None, AvailableSpace::Definite(height)) => (height, TaffyMeasureMode::FitContent), + (None, AvailableSpace::MaxContent) => (f32::INFINITY, TaffyMeasureMode::MaxContent), + (None, AvailableSpace::MinContent) => (f32::INFINITY, TaffyMeasureMode::MinContent), + }; + match node_context { + Some(NodeContext { measure_function, context }) => { + measure_function(width_measure_mode, width, height_measure_mode, height, *context).into() + } + _ => core::Size::ZERO, + } + } + ) + ); + TaffyReturnCode::Ok + }) +} + +/// Create a new Node in the TaffyTree. Returns a NodeId handle to the node. +#[no_mangle] +#[allow(clippy::missing_safety_doc)] +pub unsafe extern "C" fn TaffyTree_PrintTree(raw_tree: TaffyTreeMutRef, node_id: TaffyNodeId) -> TaffyReturnCode { + with_tree_mut!(raw_tree, tree, { + tree.inner.print_tree(node_id.into()); + TaffyReturnCode::Ok + }) +} + +// ------------------------------------------------- +// Tree manipulation +// ------------------------------------------------- + +/// Create a new Node in the TaffyTree. Returns a NodeId handle to the node. +#[no_mangle] +#[allow(clippy::missing_safety_doc)] +pub unsafe extern "C" fn TaffyTree_NewNode(raw_tree: TaffyTreeMutRef) -> TaffyResult { + with_tree_mut!(raw_tree, tree, { + // TODO: make new_leaf infallible + let node_id = tree.inner.new_leaf(core::Style::default()).unwrap(); + ok!(node_id.into()); + }) +} + +/// Remove and Free a Node within a TaffyTree +#[no_mangle] +#[allow(clippy::missing_safety_doc)] +pub unsafe extern "C" fn TaffyTree_RemoveNode(raw_tree: TaffyTreeMutRef, node_id: TaffyNodeId) -> TaffyReturnCode { + with_tree_mut!(raw_tree, tree, { + try_or!(InvalidNodeId, tree.inner.remove(node_id.into())); + ok!(TaffyReturnCode::Ok); + }) +} + +/// Remove and Free a Node within a TaffyTree +#[no_mangle] +#[allow(clippy::missing_safety_doc)] +pub unsafe extern "C" fn TaffyTree_AppendChild( + raw_tree: TaffyTreeMutRef, + parent_node_id: TaffyNodeId, + child_node_id: TaffyNodeId, +) -> TaffyReturnCode { + with_tree_mut!(raw_tree, tree, { + try_or!(InvalidNodeId, tree.inner.add_child(parent_node_id.into(), child_node_id.into())); + ok!(TaffyReturnCode::Ok); + }) +} + +// ------------------------------------------------- +// Style and Layout access +// ------------------------------------------------- + +/// Create a new Node in the TaffyTree. Returns a NodeId handle to the node. +#[no_mangle] +#[allow(clippy::missing_safety_doc)] +pub unsafe extern "C" fn TaffyTree_GetStyleMut( + raw_tree: TaffyTreeMutRef, + node_id: TaffyNodeId, +) -> TaffyResult { + with_tree_mut!(raw_tree, tree, { + let style = try_or!(InvalidNodeId, tree.inner.try_style_mut(node_id.into())); + ok!(style as *mut core::Style as TaffyStyleMutRef); + }) +} + +/// Create a new Node in the TaffyTree. Returns a NodeId handle to the node. +#[no_mangle] +#[allow(clippy::missing_safety_doc)] +pub unsafe extern "C" fn TaffyTree_SetNodeContext( + raw_tree: TaffyTreeMutRef, + node_id: TaffyNodeId, + measure_function: TaffyMeasureFunction, + context: *mut c_void, +) -> TaffyReturnCode { + with_tree_mut!(raw_tree, tree, { + try_or!( + InvalidNodeId, + tree.inner.set_node_context(node_id.into(), Some(NodeContext { measure_function, context })) + ); + ok!(TaffyReturnCode::Ok); + }) +} + +/// Create a new Node in the TaffyTree. Returns a NodeId handle to the node. +#[no_mangle] +#[allow(clippy::missing_safety_doc)] +pub unsafe extern "C" fn TaffyTree_GetLayout( + raw_tree: TaffyTreeConstRef, + node_id: TaffyNodeId, +) -> TaffyResult { + with_tree!(raw_tree, tree, { + let layout = try_or!(InvalidNodeId, tree.inner.layout(node_id.into())); + ok!(TaffyLayout { + x: layout.location.x, + y: layout.location.y, + width: layout.size.width, + height: layout.size.height + }); + }) +} diff --git a/bindings/c/src/value.rs b/bindings/c/src/value.rs new file mode 100644 index 000000000..da870581e --- /dev/null +++ b/bindings/c/src/value.rs @@ -0,0 +1,217 @@ +//! Values types for C FFI + +use taffy::prelude as core; + +use super::{TaffyFFIDefault, TaffyReturnCode}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(C)] +pub enum TaffyEdge { + /// The top edge of the box + Top, + /// The bottom edge of the box + Bottom, + /// The left edge of the box + Left, + /// The right edge of the box + Right, + /// Both the top and bottom edges of the box + Vertical, + /// Both the left and right edges of the box + Horizontal, + /// All four edges of the box + All, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(C)] +pub enum TaffyUnit { + /// A none value (used to unset optional fields) + None, + /// Fixed Length (pixel) value + Length, + /// Percentage value + Percent, + /// Min-content size + MinContent, + /// Max-content size + MaxContent, + /// fit-content() function with a pixel limit + FitContentPx, + /// fit-content() function with a percentage limit + FitContentPercent, + /// Automatic values + Auto, + /// fr unit + Fr, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(C)] +pub enum TaffyMeasureMode { + /// A none value (used to unset optional fields) + Exact, + /// Fixed Length (pixel) value + FitContent, + /// Percentage value + MinContent, + /// Min-content size + MaxContent, +} + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct TaffySize { + width: f32, + height: f32, +} +impl From for core::Size { + #[inline(always)] + fn from(value: TaffySize) -> Self { + core::Size { width: value.width, height: value.height } + } +} + +#[repr(C)] +pub struct TaffyLayout { + pub x: f32, + pub y: f32, + pub width: f32, + pub height: f32, +} +impl TaffyFFIDefault for TaffyLayout { + fn default() -> Self { + TaffyLayout { x: 0.0, y: 0.0, width: 0.0, height: 0.0 } + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(C)] +pub struct TaffyDimension { + /// The value. If the unit is variant that doesn't require a value (e.g. Auto) then the value is ignored. + pub value: f32, + pub unit: TaffyUnit, +} +impl TaffyFFIDefault for TaffyDimension { + fn default() -> Self { + Self { unit: TaffyUnit::None, value: 0.0 } + } +} + +impl TaffyDimension { + #[inline(always)] + pub fn from_raw(unit: TaffyUnit, value: f32) -> Self { + Self { unit, value } + } +} + +impl From for TaffyDimension { + fn from(value: core::LengthPercentage) -> Self { + match value { + core::LengthPercentage::Length(value) => Self { unit: TaffyUnit::Length, value }, + core::LengthPercentage::Percent(value) => Self { unit: TaffyUnit::Percent, value }, + } + } +} + +impl TryFrom for core::LengthPercentage { + type Error = TaffyReturnCode; + + fn try_from(value: TaffyDimension) -> Result { + match value.unit { + TaffyUnit::Length => Ok(core::LengthPercentage::Length(value.value)), + TaffyUnit::Percent => Ok(core::LengthPercentage::Percent(value.value)), + TaffyUnit::None => Err(TaffyReturnCode::InvalidNone), + TaffyUnit::Auto => Err(TaffyReturnCode::InvalidAuto), + TaffyUnit::MinContent => Err(TaffyReturnCode::InvalidMinContent), + TaffyUnit::MaxContent => Err(TaffyReturnCode::InvalidMaxContent), + TaffyUnit::FitContentPx => Err(TaffyReturnCode::InvalidFitContentPx), + TaffyUnit::FitContentPercent => Err(TaffyReturnCode::InvalidFitContentPercent), + TaffyUnit::Fr => Err(TaffyReturnCode::InvalidFr), + } + } +} + +impl From for TaffyDimension { + fn from(value: core::LengthPercentageAuto) -> Self { + match value { + core::LengthPercentageAuto::Length(value) => Self { unit: TaffyUnit::Length, value }, + core::LengthPercentageAuto::Percent(value) => Self { unit: TaffyUnit::Percent, value }, + core::LengthPercentageAuto::Auto => Self { unit: TaffyUnit::Auto, value: 0.0 }, + } + } +} + +impl TryFrom for core::LengthPercentageAuto { + type Error = TaffyReturnCode; + + fn try_from(value: TaffyDimension) -> Result { + match value.unit { + TaffyUnit::Auto => Ok(core::LengthPercentageAuto::Auto), + TaffyUnit::Length => Ok(core::LengthPercentageAuto::Length(value.value)), + TaffyUnit::Percent => Ok(core::LengthPercentageAuto::Percent(value.value)), + TaffyUnit::None => Err(TaffyReturnCode::InvalidNone), + TaffyUnit::MinContent => Err(TaffyReturnCode::InvalidMinContent), + TaffyUnit::MaxContent => Err(TaffyReturnCode::InvalidMaxContent), + TaffyUnit::FitContentPx => Err(TaffyReturnCode::InvalidFitContentPx), + TaffyUnit::FitContentPercent => Err(TaffyReturnCode::InvalidFitContentPercent), + TaffyUnit::Fr => Err(TaffyReturnCode::InvalidFr), + } + } +} + +impl From for TaffyDimension { + fn from(value: core::Dimension) -> Self { + match value { + core::Dimension::Length(value) => Self { unit: TaffyUnit::Length, value }, + core::Dimension::Percent(value) => Self { unit: TaffyUnit::Percent, value }, + core::Dimension::Auto => Self { unit: TaffyUnit::Auto, value: 0.0 }, + } + } +} + +impl TryFrom for core::Dimension { + type Error = TaffyReturnCode; + + fn try_from(value: TaffyDimension) -> Result { + match value.unit { + TaffyUnit::Auto => Ok(core::Dimension::Auto), + TaffyUnit::Length => Ok(core::Dimension::Length(value.value)), + TaffyUnit::Percent => Ok(core::Dimension::Percent(value.value)), + TaffyUnit::None => Err(TaffyReturnCode::InvalidNone), + TaffyUnit::MinContent => Err(TaffyReturnCode::InvalidMinContent), + TaffyUnit::MaxContent => Err(TaffyReturnCode::InvalidMaxContent), + TaffyUnit::FitContentPx => Err(TaffyReturnCode::InvalidFitContentPx), + TaffyUnit::FitContentPercent => Err(TaffyReturnCode::InvalidFitContentPercent), + TaffyUnit::Fr => Err(TaffyReturnCode::InvalidFr), + } + } +} + +/// For all fields, zero represents not set +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(C)] +pub struct TaffyGridPlacement { + pub start: i16, + pub end: i16, + pub span: u16, +} + +impl TaffyFFIDefault for TaffyGridPlacement { + fn default() -> Self { + Self { start: 0, end: 0, span: 0 } + } +} + +impl From for core::Line { + fn from(placement: TaffyGridPlacement) -> Self { + Self::from_raw_parts(placement.start, placement.span, placement.end) + } +} + +impl From> for TaffyGridPlacement { + fn from(placement: core::Line) -> Self { + let (start, span, end) = placement.into_raw_parts(); + Self { start, span, end } + } +} diff --git a/src/lib.rs b/src/lib.rs index 577abf778..ed8543b42 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,7 +59,7 @@ #![cfg_attr(not(feature = "std"), no_std)] #![deny(unsafe_code)] -#![forbid(unsafe_code)] +// #![forbid(unsafe_code)] #![warn(missing_docs)] #![warn(clippy::missing_docs_in_private_items)] diff --git a/src/prelude.rs b/src/prelude.rs index 43d12bac9..725980fc3 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -4,7 +4,7 @@ pub use crate::{ geometry::{Line, Rect, Size}, style::{ AlignContent, AlignItems, AlignSelf, AvailableSpace, Dimension, Display, JustifyContent, JustifyItems, - JustifySelf, LengthPercentage, LengthPercentageAuto, Position, Style, + JustifySelf, LengthPercentage, LengthPercentageAuto, Overflow, Position, Style, }, style_helpers::{ auto, fit_content, length, max_content, min_content, percent, zero, FromFlex, FromLength, FromPercent, diff --git a/src/style/grid.rs b/src/style/grid.rs index 5d77e3593..08d569f9e 100644 --- a/src/style/grid.rs +++ b/src/style/grid.rs @@ -161,6 +161,39 @@ impl Line { end: self.end.into_origin_zero_placement(explicit_track_count), } } + + /// Convert raw values of start, span, and end into a [`GridPlacement`]. + /// Zero is not a valid value for any of the values and is thus used to indicate unset + /// Only 2 of the 3 values should be set. If all 3 are set then `span_value` is ignored. + pub fn from_raw_parts(start: i16, span_value: u16, end: i16) -> Self { + match (start, span_value, end) { + (0, 0, 0) => auto(), + (start, 0, 0) => Line { start: line(start), end: auto() }, + (0, 0, end) => Line { start: auto(), end: line(end) }, + (0, span_value, 0) => span(span_value), + (start, span_value, 0) => Line { start: line(start), end: span(span_value) }, + (0, span_value, end) => Line { start: span(span_value), end: line(end) }, + (start, _, end) => Line { start: line(start), end: line(end) }, + } + } + + /// Convert raw values of start, span, and end into a [`GridPlacement`]. + /// Zero is not a valid value for any of the values and is thus used to indicate unset + /// Only 2 of the 3 values should be set. If all 3 are set then `span_value` is ignored. + pub fn into_raw_parts(self) -> (i16, u16, i16) { + use GenericGridPlacement::*; + match (self.start, self.end) { + (Line(start), Line(end)) => (start.as_i16(), 0, end.as_i16()), + (Line(start), Span(span)) => (start.as_i16(), span, 0), + (Line(start), Auto) => (start.as_i16(), 1, 0), + (Span(span), Line(end)) => (0, span, end.as_i16()), + (Span(span), Span(_)) => (0, span, 0), + (Span(span), Auto) => (0, span, 0), + (Auto, Line(end)) => (0, 1, end.as_i16()), + (Auto, Span(span)) => (0, span, 0), + (Auto, Auto) => (0, 1, 0), + } + } } impl Line { diff --git a/src/style/mod.rs b/src/style/mod.rs index f77973156..c48e8ce0d 100644 --- a/src/style/mod.rs +++ b/src/style/mod.rs @@ -439,64 +439,75 @@ mod tests { fn style_sizes() { use super::*; - fn assert_type_size(expected_size: usize) { + fn assert_type_size_and_align(expected_size: usize, expected_align: usize) { let name = ::core::any::type_name::(); let name = name.replace("taffy::geometry::", ""); let name = name.replace("taffy::style::dimension::", ""); let name = name.replace("taffy::style::alignment::", ""); let name = name.replace("taffy::style::flex::", ""); let name = name.replace("taffy::style::grid::", ""); + let name = name.replace("alloc::vec::", ""); + let name = name.replace("taffy::compute::grid::types::coordinates::", ""); assert_eq!( ::core::mem::size_of::(), expected_size, - "Expected {} for be {} byte(s) but it was {} byte(s)", + "Expected size_of {} to be {} byte(s) but it was {} byte(s)", name, expected_size, ::core::mem::size_of::(), ); + + assert_eq!( + ::core::mem::align_of::(), + expected_align, + "Expected align_of {} to be {} byte(s) but it was {} byte(s)", + name, + expected_align, + ::core::mem::align_of::(), + ); } // Display and Position - assert_type_size::(1); - assert_type_size::(1); - assert_type_size::(1); + assert_type_size_and_align::(1, 1); + assert_type_size_and_align::(1, 1); + assert_type_size_and_align::(1, 1); // Dimensions and aggregations of Dimensions - assert_type_size::(4); - assert_type_size::(8); - assert_type_size::(8); - assert_type_size::(8); - assert_type_size::>(16); - assert_type_size::>(16); - assert_type_size::>(16); - assert_type_size::>(32); - assert_type_size::>(32); - assert_type_size::>(32); + assert_type_size_and_align::(4, 4); + assert_type_size_and_align::(8, 4); + assert_type_size_and_align::(8, 4); + assert_type_size_and_align::(8, 4); + assert_type_size_and_align::>(16, 4); + assert_type_size_and_align::>(16, 4); + assert_type_size_and_align::>(16, 4); + assert_type_size_and_align::>(32, 4); + assert_type_size_and_align::>(32, 4); + assert_type_size_and_align::>(32, 4); // Alignment - assert_type_size::(1); - assert_type_size::(1); - assert_type_size::>(1); + assert_type_size_and_align::(1, 1); + assert_type_size_and_align::(1, 1); + assert_type_size_and_align::>(1, 1); // Flexbox Container - assert_type_size::(1); - assert_type_size::(1); + assert_type_size_and_align::(1, 1); + assert_type_size_and_align::(1, 1); // CSS Grid Container - assert_type_size::(1); - assert_type_size::(8); - assert_type_size::(12); - assert_type_size::(20); - assert_type_size::(32); - assert_type_size::>(24); - assert_type_size::>(24); + assert_type_size_and_align::(1, 1); + assert_type_size_and_align::(8, 4); + assert_type_size_and_align::(12, 4); + assert_type_size_and_align::(20, 4); + assert_type_size_and_align::(32, 8); + assert_type_size_and_align::>(24, 8); + assert_type_size_and_align::>(24, 8); // CSS Grid Item - assert_type_size::(4); - assert_type_size::>(8); + assert_type_size_and_align::(4, 2); + assert_type_size_and_align::>(8, 2); // Overall - assert_type_size::