Skip to content

samoylenkodmitry/Cranpose

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

825 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

https://codewiki.google/github.com/samoylenkodmitry/cranpose

v0.0.40.webm

🌐 Live Demo

Try it in your browser!

Cranpose

ChatGPT Image Jan 18, 2026, 10_53_13 AM

Cranpose is a declarative UI framework for Rust, inspired by Jetpack Compose. It targets Desktop (Linux, macOS, Windows), Android, and Web (WASM) from a single Rust codebase. iOS is not a supported backend until a real UIKit/CAMetalLayer platform crate exists.

The composition runtime uses Slot Table V2: active groups live in preorder group, payload, and node tables, while inactive retained branches are explicit detached subtrees. Gap-table notes are historical rationale only; the active slot-table specification is docs/cranpose_slot_table_v2_design.md.

Quick Start via Isolated Demo

To get started, use the Isolated Demo template found in apps/isolated-demo. This project is pre-configured with the dependencies and build scripts for the implemented platforms.

# Clone the repository
git clone https://github.com/samoylenkodmitry/cranpose.git
cd cranpose/apps/isolated-demo

# Run on Desktop (Linux/macOS/Windows)
cargo run --features desktop,renderer-wgpu

Example: Todo List Application

The following example demonstrates managing state, handling user input, and rendering a dynamic list.

use cranpose::prelude::*;

#[derive(Clone)]
struct TodoItem {
    id: usize,
    text: String,
    done: bool,
}

#[composable]
fn TodoApp() {
    // State management using useState
    let items = useState(|| vec![
        TodoItem { id: 0, text: "Buy milk".into(), done: false },
        TodoItem { id: 1, text: "Walk the dog".into(), done: true },
    ]);
    let input_text = useState(|| String::new());
    let next_id = useState(|| 2);

    Column(Modifier.fill_max_size().padding(20.0), || {
        Text("My Todo List", Modifier.padding(10.0).font_size(24.0));

        // Input Row
        Row(Modifier.fill_max_width().padding(5.0), || {
            BasicTextField(
                value = input_text.value(),
                on_value_change = move |new_text| input_text.set(new_text),
                Modifier.weight(1.0).padding(5.0)
            );
            
            Button(
                onClick = move || {
                    if !input_text.value().is_empty() {
                        let mut list = items.value();
                        list.push(TodoItem {
                            id: next_id.value(),
                            text: input_text.value(),
                            done: false,
                        });
                        items.set(list);
                        next_id.set(next_id.value() + 1);
                        input_text.set(String::new());
                    }
                }, 
                || Text("Add")
            );
        });
        
        // Dynamic List Rendering
        LazyColumn(Modifier.weight(1.0), || {
            items(items.value().len(), |i| {
                let item = items.value()[i].clone();
                
                Row(
                    Modifier
                        .fill_max_width()
                        .padding(5.0)
                        .clickable(move || {
                            // Toggle done status
                            let mut list = items.value();
                            if let Some(todo) = list.iter_mut().find(|t| t.id == item.id) {
                                todo.done = !todo.done;
                            }
                            items.set(list);
                        }),
                    || {
                        Text(if item.done { "[x]" } else { "[ ]" });
                        Spacer(Modifier.width(10.0));
                        Text(
                            item.text, 
                            Modifier.alpha(if item.done { 0.5 } else { 1.0 })
                        );
                    }
                );
            });
        });
    });
}

Platform Support

Platform Backend Status
Linux x86_64 Vulkan via wgpu Supported and actively tested
macOS aarch64 Metal via wgpu Experimental packaging; renderer path implemented
Windows x86_64 DX12/Vulkan via wgpu Experimental; not continuously tested
Android Vulkan/GLES via wgpu Experimental; release APK build is checked
iOS UIKit/CAMetalLayer backend Unavailable
Web (WASM) WebGPU/WebGL2 via wgpu Experimental; demo build is checked

Release artifacts are available on the Releases page for the platforms that have published builds.

Building

The apps/isolated-demo starter project shows the complete cross-platform setup. It depends only on published crates from crates.io.

Desktop (Linux/macOS/Windows)

cd apps/isolated-demo
cargo run --features desktop,renderer-wgpu

macOS .app bundles are built through the workspace task runner:

cargo xtask bundle-macos \
  --package desktop-app \
  --bin desktop-app \
  --app-name "Cranpose Demo" \
  --bundle-id io.cranpose.demo

Pass --resources <dir> to copy bundle resources into Contents/Resources, and --sign-identity <id> to run the explicit codesign step.

Android

# Prerequisites: cargo install cargo-ndk
cd apps/isolated-demo/android
./gradlew :app:assembleRelease

iOS

iOS is unavailable. The ios cargo feature is reserved for the real platform backend and does not alias desktop.

Web (WASM)

# Prerequisites: cargo install wasm-pack
cd apps/isolated-demo
./build-web.sh
python3 -m http.server 8080

Binary Size

Cranpose apps stay small when two things are set up right: the cargo profile and the feature set.

1. Add a tuned release profile to your app's Cargo.toml. Cargo profiles are taken from the top-level package, so the framework cannot set them for you. Without this, a plain cargo build --release produces a binary several times larger than necessary (no LTO, no stripping, unwinding kept):

[profile.release]
opt-level = 3         # or "z" to trade some runtime speed for size
lto = true
codegen-units = 1
strip = true
panic = "abort"

2. Pick features deliberately. The cranpose default feature set favors out-of-the-box behavior over size:

  • embedded-default-font (default): embeds the ~1.3 MiB NotoSansMerged fallback font so text renders even when the app provides no fonts. Apps that bundle fonts via AppLauncher::with_fonts should build with default-features = false to drop it.
  • renderer-wgpu-gles (off by default): the GL/GLES fallback backend for desktop machines without a working Vulkan driver. Leaving it off removes the GLES half of wgpu and naga's GLSL writer from the binary. Android always compiles the GLES fallback; web always compiles WebGL.
  • desktop-x11 / desktop-wayland: desktop compiles both display-server backends; picking one drops the other's window/input stack.
[dependencies]
cranpose = { version = "0.1.28", default-features = false, features = [
    "desktop",        # or just "desktop-wayland" / "desktop-x11"
    "renderer-wgpu",
] }

3. For the smallest binary, build with the nightly-only pipeline (build-std with a size-tuned std and immediate-abort panics):

cargo xtask dist-min --package my-app --bin my-app

Reference ladder for a minimal hello-world app on Linux x86_64 with default-features = false: ~15 MB with cargo's untuned default release profile → 8.7 MB with the profile above → 6.0 MB with the release-small profile (opt-level = "z", lto = "fat") → 3.4 MB with a single display backend + dist-min. The full breakdown and the roadmap toward smaller binaries live in docs/binary_size.md.

Verification Gates

The core workspace gates are:

cargo fmt --check
cargo test
cargo clippy
cargo build --workspace --no-default-features
cargo xtask dependency-budget
cargo xtask binary-size --package isolated-demo --bin isolated-demo --profile release-small --max-bytes 13107200

See apps/isolated-demo/README.md for full details.

License

This project is available under the terms of the Apache License (Version 2.0). See LICENSE-APACHE for the full license text.

About

Cranpose is a Jetpack Compose-inspired declarative Rust UI framework. https://crates.io/crates/cranpose

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors