kasuari-kotlin

Cassowary constraint-solving implementation for UI layout, low-level solver API with weighted strengths, editable variables for interactive updates, and dual error styles (exceptions or Result-based).

Android
JVM
iOS
Android Native
macOS
watchOS
tvOS
Linux
Windows
Wasm
JS
GitHub stars0
Dependents1
LicenseMIT License
Creation date9 months ago

Last activity1 day ago
Latest release0.1.6 (4 months ago)

Kasuari-Kotlin

Kotlin License GitHub

A Kotlin Multiplatform Native implementation of the Cassowary constraint solving algorithm (Badros et. al 2001). This is a port of the Rust kasuari library by the Ratatui team.

Kasuari is the Indonesian name for the Cassowary bird.

Overview

Cassowary is designed for solving constraints to lay out user interfaces. Constraints typically take the form "this button must line up with this text box", or "this box should try to be 3 times the size of this other box". Its most popular incarnation by far is in Apple's AutoLayout system for macOS and iOS user interfaces.

This library is a low-level interface to the solving algorithm. It does not have any intrinsic knowledge of common user interface conventions like rectangular regions or even two dimensions. These abstractions belong in a higher-level library.

Supported Platforms

  • Apple: macosArm64, iosArm64, iosSimulatorArm64, iosX64, tvosArm64, tvosSimulatorArm64, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64
  • Linux: linuxX64, linuxArm64
  • Windows: mingwX64
  • Android: Android KMP library (compileSdk 34, minSdk 24) plus androidNativeArm32, androidNativeArm64, androidNativeX86, androidNativeX64
  • JVM (jvmToolchain(21))
  • Web: js (browser + Node.js), wasmJs (browser + Node.js), wasmWasi (Node.js)

Installation

Kasuari-Kotlin is published to Maven Central as io.github.kotlinmania:kasuari-kotlin.

dependencies {
    implementation("io.github.kotlinmania:kasuari-kotlin:0.1.6")
}

For a Kotlin Multiplatform consumer, add it to the relevant source set:

kotlin {
    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("io.github.kotlinmania:kasuari-kotlin:0.1.6")
            }
        }
    }
}

Quick Start

import kasuari.*

// Create a solver
val solver = Solver.new()

// Create variables
val left = Variable.new()
val width = Variable.new()
val right = Variable.new()

// Add constraints: right == left + width
solver.addConstraint(
    right with WeightedRelation.EQ(Strength.REQUIRED) to (left + width)
)

// left == 0
solver.addConstraint(
    left with WeightedRelation.EQ(Strength.REQUIRED) to 0.0
)

// width == 100 (strong, not required)
solver.addConstraint(
    width with WeightedRelation.EQ(Strength.STRONG) to 100.0
)

// Read the solution
println("left: ${solver.getValue(left)}")    // 0.0
println("width: ${solver.getValue(width)}")  // 100.0
println("right: ${solver.getValue(right)}")  // 100.0

Edit Variables

For interactive applications, use edit variables to dynamically change values:

val solver = Solver.new()
val x = Variable.new()

// Add a constraint that x >= 0
solver.addConstraint(x with WeightedRelation.GE(Strength.REQUIRED) to 0.0)

// Register x as an edit variable
solver.addEditVariable(x, Strength.STRONG)

// Suggest values for x
solver.suggestValue(x, 50.0)
println(solver.getValue(x))  // 50.0

solver.suggestValue(x, -10.0)
println(solver.getValue(x))  // 0.0 (constrained to >= 0)

Error Handling

The library provides two styles of error handling:

Exception-based (default)

try {
    solver.addConstraint(constraint)
} catch (e: AddConstraintError.DuplicateConstraint) {
    println("Constraint already exists")
} catch (e: AddConstraintError.UnsatisfiableConstraint) {
    println("Constraint conflicts with existing constraints")
}

Result-based (Rust-style)

when (val result = solver.tryAddConstraint(constraint)) {
    is Result.Ok -> println("Constraint added")
    is Result.Err -> when (result.error) {
        is AddConstraintError.DuplicateConstraint -> println("Already exists")
        is AddConstraintError.UnsatisfiableConstraint -> println("Conflicts")
        is AddConstraintError.InternalSolver -> println("Internal error")
    }
}

Constraint Strengths

Constraints have strengths that determine priority when conflicts arise:

  • Strength.REQUIRED - Must be satisfied (solver fails if impossible)
  • Strength.STRONG - High priority, but can be violated
  • Strength.MEDIUM - Medium priority
  • Strength.WEAK - Low priority, used for defaults/preferences
// Required: x must equal 100
val required = x with WeightedRelation.EQ(Strength.REQUIRED) to 100.0

// Strong: x should be at least 0
val strong = x with WeightedRelation.GE(Strength.STRONG) to 0.0

// Weak: x prefers to be 50
val weak = x with WeightedRelation.EQ(Strength.WEAK) to 50.0

License

Licensed under

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the MIT license, shall be licensed as above, without any additional terms or conditions.


Acknowledgments

This Kotlin Multiplatform port was created by Sydney Renee of The Solace Project for KotlinMania.

Special thanks to the original authors and contributors:

  • Dylan Ede - Original Cassowary-rs Rust implementation (2016)
  • Josh McKinney and the Ratatui team - kasuari fork and maintenance (2024)
  • The authors of the C++ Kiwi library, which heavily influenced the implementation
  • Badros, Borning, and Stuckey - Original Cassowary algorithm paper (2001)
Android
JVM
iOS
Android Native
macOS
watchOS
tvOS
Linux
Windows
Wasm
JS
GitHub stars0
Dependents1
LicenseMIT License
Creation date9 months ago

Last activity1 day ago
Latest release0.1.6 (4 months ago)

Kasuari-Kotlin

Kotlin License GitHub

A Kotlin Multiplatform Native implementation of the Cassowary constraint solving algorithm (Badros et. al 2001). This is a port of the Rust kasuari library by the Ratatui team.

Kasuari is the Indonesian name for the Cassowary bird.

Overview

Cassowary is designed for solving constraints to lay out user interfaces. Constraints typically take the form "this button must line up with this text box", or "this box should try to be 3 times the size of this other box". Its most popular incarnation by far is in Apple's AutoLayout system for macOS and iOS user interfaces.

This library is a low-level interface to the solving algorithm. It does not have any intrinsic knowledge of common user interface conventions like rectangular regions or even two dimensions. These abstractions belong in a higher-level library.

Supported Platforms

  • Apple: macosArm64, iosArm64, iosSimulatorArm64, iosX64, tvosArm64, tvosSimulatorArm64, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64
  • Linux: linuxX64, linuxArm64
  • Windows: mingwX64
  • Android: Android KMP library (compileSdk 34, minSdk 24) plus androidNativeArm32, androidNativeArm64, androidNativeX86, androidNativeX64
  • JVM (jvmToolchain(21))
  • Web: js (browser + Node.js), wasmJs (browser + Node.js), wasmWasi (Node.js)

Installation

Kasuari-Kotlin is published to Maven Central as io.github.kotlinmania:kasuari-kotlin.

dependencies {
    implementation("io.github.kotlinmania:kasuari-kotlin:0.1.6")
}

For a Kotlin Multiplatform consumer, add it to the relevant source set:

kotlin {
    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("io.github.kotlinmania:kasuari-kotlin:0.1.6")
            }
        }
    }
}

Quick Start

import kasuari.*

// Create a solver
val solver = Solver.new()

// Create variables
val left = Variable.new()
val width = Variable.new()
val right = Variable.new()

// Add constraints: right == left + width
solver.addConstraint(
    right with WeightedRelation.EQ(Strength.REQUIRED) to (left + width)
)

// left == 0
solver.addConstraint(
    left with WeightedRelation.EQ(Strength.REQUIRED) to 0.0
)

// width == 100 (strong, not required)
solver.addConstraint(
    width with WeightedRelation.EQ(Strength.STRONG) to 100.0
)

// Read the solution
println("left: ${solver.getValue(left)}")    // 0.0
println("width: ${solver.getValue(width)}")  // 100.0
println("right: ${solver.getValue(right)}")  // 100.0

Edit Variables

For interactive applications, use edit variables to dynamically change values:

val solver = Solver.new()
val x = Variable.new()

// Add a constraint that x >= 0
solver.addConstraint(x with WeightedRelation.GE(Strength.REQUIRED) to 0.0)

// Register x as an edit variable
solver.addEditVariable(x, Strength.STRONG)

// Suggest values for x
solver.suggestValue(x, 50.0)
println(solver.getValue(x))  // 50.0

solver.suggestValue(x, -10.0)
println(solver.getValue(x))  // 0.0 (constrained to >= 0)

Error Handling

The library provides two styles of error handling:

Exception-based (default)

try {
    solver.addConstraint(constraint)
} catch (e: AddConstraintError.DuplicateConstraint) {
    println("Constraint already exists")
} catch (e: AddConstraintError.UnsatisfiableConstraint) {
    println("Constraint conflicts with existing constraints")
}

Result-based (Rust-style)

when (val result = solver.tryAddConstraint(constraint)) {
    is Result.Ok -> println("Constraint added")
    is Result.Err -> when (result.error) {
        is AddConstraintError.DuplicateConstraint -> println("Already exists")
        is AddConstraintError.UnsatisfiableConstraint -> println("Conflicts")
        is AddConstraintError.InternalSolver -> println("Internal error")
    }
}

Constraint Strengths

Constraints have strengths that determine priority when conflicts arise:

  • Strength.REQUIRED - Must be satisfied (solver fails if impossible)
  • Strength.STRONG - High priority, but can be violated
  • Strength.MEDIUM - Medium priority
  • Strength.WEAK - Low priority, used for defaults/preferences
// Required: x must equal 100
val required = x with WeightedRelation.EQ(Strength.REQUIRED) to 100.0

// Strong: x should be at least 0
val strong = x with WeightedRelation.GE(Strength.STRONG) to 0.0

// Weak: x prefers to be 50
val weak = x with WeightedRelation.EQ(Strength.WEAK) to 50.0

License

Licensed under

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the MIT license, shall be licensed as above, without any additional terms or conditions.


Acknowledgments

This Kotlin Multiplatform port was created by Sydney Renee of The Solace Project for KotlinMania.

Special thanks to the original authors and contributors:

  • Dylan Ede - Original Cassowary-rs Rust implementation (2016)
  • Josh McKinney and the Ratatui team - kasuari fork and maintenance (2024)
  • The authors of the C++ Kiwi library, which heavily influenced the implementation
  • Badros, Borning, and Stuckey - Original Cassowary algorithm paper (2001)