Files
2026-01-31 21:01:30 +01:00

7.3 KiB

sidebar_position, title
sidebar_position title
6 Macros API

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.