Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
c1bb7a9
wip: java bindings
adjabaev Aug 19, 2024
21ec83d
Pretty much usable (there are missing functions such as remove and so…
adjabaev Aug 20, 2024
65db9eb
rust fmt formatting (did i figure it out?)
adjabaev Aug 20, 2024
4fe722b
Fix Clippy issues
adjabaev Aug 20, 2024
9891bbb
java: turn into a maven project
adjabaev Aug 20, 2024
47bea2e
java: implement a few tests to ensure everything is working as intended
adjabaev Aug 21, 2024
3eabd6c
rust-java: Add most of methods in TaffyTree
adjabaev Aug 21, 2024
587c810
rust: rustfmt
adjabaev Aug 21, 2024
81dcdcd
java: utility
adjabaev Aug 21, 2024
b53ea74
rust: fix signatures
adjabaev Aug 21, 2024
a0dab92
rust-java: remove Taffy from classes & change signatures
adjabaev Aug 21, 2024
d778ff2
enums: enum generator (just to collect feedback)
adjabaev Aug 26, 2024
6d1beb2
enums: fix index parameter as we can get it with ordinal()
adjabaev Aug 26, 2024
71064b0
enums: write files to enum folder
adjabaev Aug 26, 2024
32865ee
enums: use autogenerated files
adjabaev Aug 26, 2024
1fd2c5b
enums: update refs
adjabaev Aug 27, 2024
8bf25f1
java: readded first example for reference
adjabaev Aug 28, 2024
5bae96d
java: add nested and somewhat tweak toString's
adjabaev Aug 28, 2024
9b412c8
java: gap & related util
adjabaev Aug 28, 2024
cbdb98c
rust: fix enum field stuff
adjabaev Aug 28, 2024
114a279
rust/ java: wip - more complex examples
adjabaev Aug 29, 2024
560fd19
genenums: move to to scripts folder
adjabaev Sep 2, 2024
9db2b69
genenums: auto generate "transformers"
adjabaev Sep 2, 2024
1697115
genenums: fix generics
adjabaev Sep 2, 2024
efebe81
genenums: add subfolders
adjabaev Sep 2, 2024
1735861
genenums: support default & remove usage of HashMap as it doesn't kee…
adjabaev Sep 2, 2024
a10e904
genenums: tweak file end line generation
adjabaev Sep 2, 2024
38b6576
genenums: try to fix rustfmt
adjabaev Sep 2, 2024
d7090fb
genenums: try to fix rustfmt
adjabaev Sep 2, 2024
798a82a
genenums: rustfmt stuff
adjabaev Sep 2, 2024
59a793f
genenums: add note for when intersperse is stabilised
adjabaev Sep 3, 2024
765ccd2
java: working on measure function stuff
adjabaev Sep 3, 2024
f55de14
Merge branch 'main' into java-bindings
adjabaev Apr 24, 2026
6fae54a
java: replace bindings with upstream-shaped JNI MVP
adjabaev Apr 24, 2026
af4bc4b
fix: clippy warnings
adjabaev Apr 24, 2026
5423f2c
fix: clippy warnings in genenums
adjabaev Apr 24, 2026
f822799
feat: add gradle wrappers
adjabaev Apr 24, 2026
a8c1c3f
feat: complete implementation
adjabaev Apr 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions bindings/java/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Rust
rust/target/

# Gradle
java/.gradle/
java/build/
109 changes: 109 additions & 0 deletions bindings/java/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Taffy Java bindings

JNI bindings to the [Taffy](../../README.md) layout engine.

Status: **WIP / MVP**. The flex and block subsets of the API are wired up;
CSS Grid, measure functions, node context, and `calc()` are not yet exposed.

## Layout

- `rust/` — a `cdylib` Rust crate (`taffy_java`) containing the JNI entry
points. It is **not** part of the main Taffy workspace so a normal
`cargo build` in the repo root does not require a JDK.
- `java/` — a Gradle project that compiles to `taffy-java-<version>.jar`
and runs JUnit tests against the freshly built cdylib.

## Requirements

- Rust 1.71+ (matches Taffy's MSRV)
- JDK 8+ for consumers, JDK 17+ to run Gradle
- Gradle 8+ (tested with 9.4)

## Building

From `bindings/java/java/`:

```sh
gradle build # compiles cargo + javac, packages jar
gradle test # runs JUnit tests against the local cdylib
```

`:cargoBuild` shells out to `cargo build --release` in `../rust` and wires
the output directory into `java.library.path` for tests via the
`taffy.native.dir` system property.

## Using the library

```java
import com.dioxuslabs.taffy.*;

try (TaffyTree tree = new TaffyTree()) {
long child = tree.newLeaf(Style.builder()
.size(Dimension.percent(0.5f), Dimension.AUTO));

long root = tree.newWithChildren(Style.builder()
.size(Dimension.length(100f), Dimension.length(100f))
.justifyContent(JustifyContent.CENTER),
child);

tree.computeLayout(root, 100f, 100f);

Layout rootLayout = tree.layout(root);
Layout childLayout = tree.layout(child);
}
```

Every `TaffyTree` owns a native handle and must be closed (try-with-resources
is the idiomatic pattern). Nodes are plain `long` ids scoped to the tree
that produced them.

## Loading the native library

`NativeLoader` looks for the cdylib in this order:

1. `-Dtaffy.native.path=/abs/path/to/libtaffy_java.dylib` — explicit file
2. `-Dtaffy.native.dir=/abs/path/to/dir` — directory; the name is derived from
`System.mapLibraryName("taffy_java")`
3. `/native/<os>-<arch>/<mappedName>` on the classpath (for shipping a jar
with bundled binaries — not produced by the default build yet)
4. `System.loadLibrary("taffy_java")` using `java.library.path`

## Scope (MVP)

Exposed today:

- `TaffyTree`: create, drop, enable/disable rounding, `newLeaf`,
`newWithChildren`, `setStyle`, `addChild`, `removeChild`, `childCount`,
`markDirty`, `remove`, `computeLayout`, `layout`.
- `Style`: `display`, `position`, `boxSizing`, `overflow`, `scrollbarWidth`,
`size` / `minSize` / `maxSize`, `margin`, `inset`, `padding`, `border`,
`gap`, `flexDirection`, `flexWrap`, `flexGrow`, `flexShrink`, `flexBasis`,
`justifyContent`, `alignContent`, `alignItems`, `alignSelf`, `aspectRatio`.
- Value types: `Dimension`, `LengthPercentage`, `LengthPercentageAuto`,
`AvailableSpace`.
- Enums: `Display`, `Position`, `BoxSizing`, `Overflow`, `FlexDirection`,
`FlexWrap`, `JustifyContent`, `AlignContent`, `AlignItems`, `AlignSelf`.

Not yet exposed:

- CSS Grid (`grid-template-rows`/`columns`, `grid-auto-*`, placement, gaps).
- Block-specific properties (`text-align`, float/clear).
- Measure functions / node context (needed for text nodes).
- `calc()` values.
- Detailed layout info (grid track info, etc.).
- Packaged binaries per OS/arch in the published jar.

## Design notes

- **Handles.** The Java side stores opaque `long` pointers for the tree and
for transient native `Style` objects. All handles are owned — the JVM
frees them deterministically, not via finalization.
- **Style materialization.** `Style` is a Java-side bag of optional fields.
When a node is created it spins up a fresh native `Style` via JNI setters
and discards it immediately after. This favors clarity over raw throughput;
if needed we can later add a batched/serialized path.
- **Error reporting.** Taffy errors (`TaffyError`) are thrown as
`com.dioxuslabs.taffy.TaffyException` across the JNI boundary.
- **Tagged value types.** `Dimension` and friends are encoded as a
`(tag, float)` pair instead of a subclass hierarchy; this keeps the JNI
ABI simple — every setter takes primitives only.
81 changes: 81 additions & 0 deletions bindings/java/java/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
plugins {
`java-library`
}

group = "com.dioxuslabs"
version = "0.1.0-SNAPSHOT"

java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

repositories {
mavenCentral()
}

dependencies {
testImplementation(platform("org.junit:junit-bom:5.10.2"))
testImplementation("org.junit.jupiter:junit-jupiter")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

// ---- Rust build integration ------------------------------------------------

val rustProjectDir = layout.projectDirectory.dir("../rust")
val rustTargetDir = rustProjectDir.dir("target/release")

val libFileName: String = run {
val osName = System.getProperty("os.name").lowercase()
when {
osName.contains("mac") || osName.contains("darwin") -> "libtaffy_java.dylib"
osName.contains("windows") -> "taffy_java.dll"
else -> "libtaffy_java.so"
}
}

val cargoBuild by tasks.registering(Exec::class) {
group = "build"
description = "Compile the taffy_java Rust cdylib (release)."
workingDir = rustProjectDir.asFile
commandLine("cargo", "build", "--release")
inputs.dir(rustProjectDir.dir("src"))
inputs.file(rustProjectDir.file("Cargo.toml"))
outputs.file(rustTargetDir.file(libFileName))
}

tasks.named<Test>("test") {
dependsOn(cargoBuild)
useJUnitPlatform()
// Point NativeLoader at the freshly built cdylib.
systemProperty("taffy.native.dir", rustTargetDir.asFile.absolutePath)
// JDK 22+ emits a warning when System.load is called without explicit
// native-access enablement. Opt in here so tests run clean.
jvmArgs("--enable-native-access=ALL-UNNAMED")
testLogging {
events("passed", "failed", "skipped")
showStandardStreams = true
}
}

// ---- Examples --------------------------------------------------------------
// Each file under src/examples/java/com/dioxuslabs/taffy/examples/<Name>.java
// becomes a `./gradlew example<Name>` task, analogous to `cargo run --example`.

val examples by sourceSets.creating {
java.srcDir("src/examples/java")
compileClasspath += sourceSets["main"].output
runtimeClasspath += sourceSets["main"].output
}

listOf("Basic", "FlexboxGap", "Nested", "Measure", "GridHolyGrail").forEach { name ->
tasks.register<JavaExec>("example$name") {
group = "examples"
description = "Run the $name example."
dependsOn(cargoBuild, tasks.named("examplesClasses"))
classpath = examples.runtimeClasspath
mainClass.set("com.dioxuslabs.taffy.examples.$name")
systemProperty("taffy.native.dir", rustTargetDir.asFile.absolutePath)
jvmArgs("--enable-native-access=ALL-UNNAMED")
}
}
Binary file not shown.
7 changes: 7 additions & 0 deletions bindings/java/java/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading