Skip to main content
Version: 0.2.0

Macros Module API Reference

The osui-macros crate provides the procedural macros that enhance OSUI's ergonomics and enable its declarative UI syntax. These macros transform your Rust code into the necessary OSUI component and rendering structures.

#[component] Attribute Macro

#[proc_macro_attribute]
pub fn component(_attr: TokenStream, item: TokenStream) -> TokenStream { /* ... */ }

The #[component] attribute macro transforms a standard Rust function into a fully-fledged OSUI component.

Purpose:

  • Prop Generation: It automatically parses the function's parameters (after the initial cx: &Arc<Context>) and generates a struct with matching fields. These fields become the component's "props".
  • ComponentImpl Implementation: It implements the ComponentImpl trait for the generated struct, making it a valid OSUI component that can be rendered. The call method of this trait simply invokes your original function.
  • Ergonomics: Simplifies component definition by allowing you to write components as regular functions with clear parameters, without manually defining structs and ComponentImpl boilerplate.

Usage:

use osui::prelude::*; // For Context and View types
use std::sync::Arc;

#[component]
pub fn MyComponent(cx: &Arc<Context>, message: &str, count: &usize) -> View {
// Your component logic, accessing `message` and `count` directly
rsx! {
format!("Message: {}, Count: {}", message, count)
}.view(&cx)
}

// How `MyComponent` would be used in RSX:
// rsx! {
// MyComponent { message: "Hello", count: 123 }
// }

Generated Code (Simplified):

pub struct MyComponent {
pub message: String, // Note: `&str` becomes `String`
pub count: usize, // Note: `&usize` becomes `usize`
}

impl MyComponent {
pub fn component(
cx: &Arc<Context>,
message: &str, // Original function signature as `component` method
count: &usize,
) -> View {
// Original body of the function
rsx! {
format!("Message: {}, Count: {}", message, count)
}.view(&cx)
}
}

impl ComponentImpl for MyComponent {
fn call(&self, cx: &Arc<Context>) -> View {
Self::component(
cx,
&self.message, // Passes stored props as references
&self.count,
)
}
}

Requirements:

  • The function must take cx: &Arc<Context> as its first parameter.
  • The function must return View.
  • Prop parameters are typically references (e.g., &str, &i32). The macro automatically converts them to their owned types (e.g., String, i32) in the generated struct.

rsx! Procedural Macro

#[proc_macro]
pub fn rsx(input: TokenStream) -> TokenStream { /* ... */ }

The rsx! macro provides a declarative, React-like syntax for building UI component hierarchies directly in your Rust code. It parses the input and transforms it into calls to the osui::frontend::Rsx builder methods.

Purpose:

  • Declarative UI: Allows you to describe what your UI should look like, rather than imperatively writing drawing commands.
  • Component Composition: Enables easy nesting and passing of props/children to other components.
  • Reactive Flow: Integrates with OSUI's state management to define dynamic UI segments.

Syntax Overview:

The rsx! macro supports several types of nodes:

  1. Text Literals:

    rsx! {
    "Hello World"
    "Another line of text"
    }
    // Generates:
    // r.static_scope(move |scope| {
    // scope.view(Arc::new(move |ctx| {
    // ctx.draw_text(Point { x: 0, y: 0 }, &format!("Hello World"))
    // }));
    // // ... for another line
    // });
  2. Rust Expressions (@{expr}):

    let name = "Alice";
    rsx! {
    @{format!("Hello, {}!", name)}
    @{1 + 2} // Any `Display` impl
    }
    // Generates:
    // r.child(format!("Hello, {}!", name));
    // r.child(1 + 2);
    • The expr must evaluate to a type that implements osui::frontend::ToRsx.
  3. Component Instantiation (Component { prop: value, ... children }):

    #[component] fn MyDiv(cx: &Arc<Context>, content: &str) -> View { /* ... */ }
    rsx! {
    MyDiv {
    content: "Some text", // Prop
    rsx! { "Child content" } // Children (if `children: &Rsx` is a prop)
    }
    }
    // Generates:
    // r.static_scope(move |scope| {
    // scope.child(
    // MyDiv {
    // content: "Some text".to_string(), // Owned type for struct field
    // children: osui::frontend::Rsx(/* ... */)
    // },
    // None
    // );
    // });
    • path: The path to the component struct (e.g., MyDiv, my_module::MyComponent).
    • props: key: value pairs for component properties.
    • children: Any rsx! content directly inside the braces after props. This is collected into the special children: &Rsx prop if the component defines it.
  4. Conditional Rendering (@if condition { ... } [else { ... }]):

    let show = true;
    rsx! {
    %show @if show {
    "Content shown if 'show' is true"
    } else {
    "Content shown if 'show' is false"
    }
    }
    // Generates:
    // r.dynamic_scope(move |scope| {
    // if show {
    // // ... rsx for true branch
    // } else {
    // // ... rsx for false branch
    // }
    // }, vec![Arc::new(show) as Arc<dyn HookDependency>]);
    • %dep1, dep2: Optional dependency list. The if block will re-evaluate when any of these dependencies (which must implement HookDependency, like State<T>) change.
    • condition: A Rust expression evaluating to bool.
    • { ... }: An rsx! fragment rendered if condition is true.
    • else { ... }: Optional rsx! fragment rendered if condition is false.
  5. Loop Rendering (@for pattern in expr { ... }):

    let items = vec!["A", "B", "C"];
    rsx! {
    %items @for item in items {
    format!("Item: {}", item)
    }
    }
    // Generates:
    // r.dynamic_scope(move |scope| {
    // for item in items {
    // // ... rsx for each item
    // }
    // }, vec![Arc::new(items) as Arc<dyn HookDependency>]);
    • %dep1, dep2: Optional dependency list. The for loop will re-evaluate when any of these dependencies change.
    • pattern: A standard Rust for loop pattern (e.g., item, (idx, item)).
    • expr: A Rust expression evaluating to an IntoIterator.
    • { ... }: An rsx! fragment rendered for each iteration.
  6. Mount Hook (!mount_hook_instance):

    let my_manual_mount = use_mount_manual();
    rsx! {
    !my_manual_mount
    }
    // Generates:
    // my_manual_mount.mount();
    • Calls the .mount() method on the provided Mount instance. This is typically used with use_mount_manual to trigger effects at a specific point in the render tree.

The rsx! macro is a powerful tool for declarative UI construction, abstracting away the underlying Rsx object manipulation and Scope creation logic.

Next: Explore the detailed Render API.