Crates.io | compo-macros |
lib.rs | compo-macros |
version | 0.1.1 |
created_at | 2025-09-12 00:29:36.731894+00 |
updated_at | 2025-09-18 15:03:20.085448+00 |
description | Compo is a general-purpose declarative and reactive component framework designed for single-threaded asynchronous execution, offering high performance and safety guarantees. Suitable for GUI scenarios or other similar non-GUI component systems. |
homepage | |
repository | https://github.com/mzdk100/compo.git |
max_upload_size | |
id | 1834773 |
size | 35,602 |
Compo is a general-purpose declarative and reactive component framework designed for single-threaded asynchronous
execution, offering high performance and safety guarantees.
It does not include any pre-implemented components but provides a #[component]
macro and essential type exports,
completely independent of third-party libraries.
Suitable for GUI scenarios or other similar non-GUI component systems.
#[component]
macro.Send
/Sync
or 'static
constraints, and no cross-thread synchronization
mechanisms.Add Compo to your Cargo.toml
:
cargo add compo
Here's a simple example demonstrating how to define and use components:
use compo::prelude::*;
fn main() {
run(app);
}
#[component]
async fn app() {
#[render] // Rendering is deferred to the next polling
row {};
#[render] // Component parameters can be omitted (using default values)
button {};
let tick_listener = Default::default(); // Create an event listener
#[render]
countdown {
on_tick: tick_listener, // If a child component emits an event, the listener can receive it
};
while let Some(i) = tick_listener.listen().await.as_ref() {
// Receive events from the countdown component
println!("{} seconds.", i);
}
println!("Hello, app!");
}
#[component]
async fn row() {
let mut text = "Hello";
#[render] // Renders a component, re-renders child component if dependent variables change
button {
text: text,
width: 32,
};
text = "world"; // Will re-render the button component
}
#[component]
async fn button(#[default = "hello"] text: &str, #[doc = "width"] width: u32) {
#[field]
// Fields are automatically added to internal struct (not exposed), values can be changed but won't trigger re-render of dependent child components
let id: i32 = 0;
println!("{}, {}", text, id);
*id = 1; // Field lifetime is same as run function, so value can be reused across multiple renders
}
#[component]
async fn countdown(#[event] on_tick: Option<u32>) {
// Emit events for the parent component to receive
for i in (0..10).rev() {
let _ = on_tick.emit(Some(i));
sleep(Duration::from_secs(1)).await;
}
let _ = on_tick.emit(None);
}
When you run this example with cargo run --example basic
, you'll see the following output:
hello, 0
9 seconds.
world, 0
world, 1
8 seconds.
7 seconds.
6 seconds.
5 seconds.
4 seconds.
3 seconds.
2 seconds.
1 seconds.
0 seconds.
Hello, app!
This output demonstrates:
For more advanced usage of the compo library, please refer to the examples in the examples directory.
#[component]
MacroUsed to define components. Components must be asynchronous functions and support rendering child components and reactive updates.
#[render]
AttributeMarks child components for rendering. If dependent variables change, the child component will re-render.
#[field]
AttributeDefines internal fields for components, with lifetimes matching the run
function.
#[event]
AttributeMarks a component parameter as an event emitter. This allows child components to send events to their parent components. The parameter should be of type Option<T>
, where T
is the type of data to be emitted. Events can be emitted using the .emit()
method and received by the parent component using the .listen().await
method.
Issues and Pull Requests are welcome!
Apache-2.0