← Return to /software

UI Composer

#graphics
#ui-composer

UI composer is a fast, modern native UI Rendering library. It combines the speed and simplicity of retained-mode native UI libraries (Qt, GTK), the amazing developer experience of web libraries (React, Vue) and the simplicity of immediate mode libraries (ImGUI).

As of now, UI Composer is in an early "research stage" and should not be used for anything critical. I really do welcome experimentation and discussion, though!

The library is written in Rust.

Getting Started

Add ui-composer as a git dependency from here. In the future, you'll be able to cargo add ui-composer.

use ui_composer::{prelude::*, std::*};

fn main() {
	UIComposer::run(Window(Label("Hello, World!")))
}

And you're ready to ship!

API Design

Where this library shines is on its API design. Instead of having an extensive library of features powered by hacky magic, UI Composer only has a handful of concepts packaged into quanta of functionality each which focuses on doing a single thing well. Apps are creating by composing (eh?) these primitives together.

There are three types of components to be precise: State, Input and Output.

use ui_composer::prelude::*;
use ui_composer::std::{Label, Button};

fn main() {  
	// State
    let text_state = Mutable::new("");  

	let app = Row(
		// Input - Modifies state...
		Button(
			Label("Click me!"),
			text_state.effect(|_| "Thank you!")
		),
		// Output - Visualizes state, possibly reactively!
		text_state()
			.signal()
			.map(|text| Label(text)),
	);

    UIComposer::run(Window(app));
}

This means that sometimes you'll miss the granularity that dozens of React Hooks give you, and the library has a steeper learning curve. But as a result, after you learn ui-composer, the code is simple to read reason about. In the example above it's clear what gets re-rendered when text_state changes (only the Label, and not the Button, since that's what inside .map).

The library does come with a lot of tasty features for convenience and ergonomics as well as a "standard library" of components for each platform it supports.

// Data
struct PersonForm {
	name: Mutable<String>,
	description: Mutable<String>,
}

// Editor
fn PersonFormEditor(person_form: PersonForm, send_fx: impl Effect) -> impl UI {
	// notice how instead of using CSS we *compose* layout by using newtypes.
	Center(
		WithSize( Extent2::new(300.0, 300.0),
			Flex! [
				[ _ ] Label("Log in!"),
				[ _ ] Row![ Label("Name"), TextEdit(form.name) ],
				[1.0] ( // <- `flex-grow: 1;`
					Column![
						Label("Tell us about yourself"),
						TextEdit(form.description)
					]
				),
				[ _ ] Button(Label("Send"), send_fx),
				// Notice how instead of passing a &str,
				// we pass an item: `Label`. This is so we could
				// make a button containing anything else we desire.
				//
				// Good design is in the little things, isn't it?
			]
		)
	)
}

But they all desugar to things you can write yourself. There's no built-in "widgets" you have to use... I invite you to read the source code of Button to see for yourself that it simply desugars to a handful of primitives.

Features

Implementation Details

Contributing

I don't accept code contributions yet, but discussions are welcome. Reach me at Bluesky or send an email to my email;