Skip to main content
Version: 0.1.0

Macros API Reference

OSUI provides several procedural macros to simplify the definition of common structures like events, components, and especially UI layouts using a declarative syntax.

event! Macro

Declares a struct that implements the Event trait. This simplifies the creation of custom event types for OSUI's reactive system.

Syntax

event!(Name); // Unit struct
event!(Name { field: Type, ... }); // Named struct with fields
event!(Name (Type, Type, ...)); // Tuple struct

Examples

use osui::prelude::*;

// Defines a unit struct `Clicked` that implements `osui::extensions::Event`.
event!(Clicked);

// Defines a named struct `Resized` with `width` and `height` fields, implements `Event`.
event!(Resized { width: u32, height: u32 });

// Defines a tuple struct `Moved` with two `u32` fields, implements `Event`.
event!(Moved(u32, u32));

How it Works

The macro automatically adds the necessary #[derive(Debug, Clone)] and the impl Event for ... block, including the as_any() method required for type erasure.

component! Macro

Declares a struct that implements the Component trait. Components allow widgets to extend their behavior or contain additional data. This macro helps avoid boilerplate.

Syntax

component!(Name); // Unit struct
component!(Name { field: Type, ... }); // Named struct with fields
component!(Name (Type, Type, ...)); // Tuple struct

Examples

use osui::prelude::*;

// Defines a unit struct `Focusable` that implements `osui::widget::Component`.
component!(Focusable);

// Defines a named struct `Tooltip` with a `text` field, implements `Component`.
component!(Tooltip { text: String });

// Defines a tuple struct `Size` with two `u32` fields, implements `Component`.
component!(Size(u32, u32));

How it Works

Similar to event!, this macro adds #[derive(Debug, Clone)] and the impl Component for ... block, providing as_any() and as_any_mut().

event_handler! Macro

Creates an event handler closure that safely calls a method on self within a move closure, handling the lifetime issues for Arc or raw pointers.

Syntax

event_handler!($self_ty:ty, $self:ident, $events:ident, $method:ident)
  • $self_ty: The type of self (e.g., MyStruct).
  • $self: The instance variable (e.g., self).
  • $events: The event source object (e.g., a Handler::new call or widget.on(...) if such an API existed) where you want to register the handler.
  • $method: The method on $self to be called when the event occurs.

Example

use osui::prelude::*;
use std::sync::Arc;
use crossterm::event::{KeyCode, KeyEvent, Event as CrosstermEvent};

struct MyApp {
screen: Arc<Screen>,
counter: State<u32>,
}

impl MyApp {
fn new(screen: Arc<Screen>) -> Self {
Self {
screen: screen.clone(),
counter: use_state(0),
}
}

// Method that will handle the event
fn handle_key_event(&mut self, _widget: &Arc<Widget>, event: &CrosstermEvent) {
if let CrosstermEvent::Key(KeyEvent { code: KeyCode::Char('a'), .. }) = event {
**self.counter.get() += 1;
println!("'a' pressed! Counter: {}", self.counter.get_dl());
}
}

fn build_ui(mut self: Arc<Self>) -> Rsx { // Self must be Arc<Self> here for cloning
let counter_dep = self.counter.clone();
rsx! {
// Attach a Handler component to the root widget
@Handler::new({
let self_ref = Arc::downgrade(&self); // Use Weak for self-referential closures if needed for more complex scenarios, or clone Arc directly.
// For direct method calls, a raw pointer cast is used by the macro,
// but generally it's safer to clone Arcs for closures or use Weak.
// The macro's internal implementation uses unsafe raw pointer:
// let self_ptr = &*self as *const Self as *mut Self;
// move |widget, event| unsafe { (*self_ptr).handle_key_event(widget, event) }
// Let's use a safe Arc clone here for demonstration
let app_clone = self.clone();
move |widget, event| {
app_clone.clone().handle_key_event(widget, event);
}
});
%counter_dep
Div {
"Press 'a' to increment: {counter_dep}"
}
}
}
}

// NOTE: The `event_handler!` macro as provided in the source code uses `unsafe` raw pointers.
// In real-world code, using `Arc::clone` or `Arc::downgrade` (for weak references)
// and then `upgrade()` within the closure is generally safer and idiomatic for
// closures that need to refer back to their owning struct.
// The provided macro is a low-level utility and care must be taken regarding lifetimes.

Safety Considerations

The provided event_handler! macro internally uses unsafe code to cast self to a raw mutable pointer and dereference it within the closure. This is inherently unsafe because it bypasses Rust's borrow checker. You must ensure that the instance referred to by the raw pointer outlives the closure. In complex scenarios (e.g., where the event handler could outlive the original struct), this can lead to use-after-free or data races. For safer patterns, consider:

  • Cloning Arcs for each capture in the closure.
  • Using Arc::downgrade for weak references if the closure might outlive the original Arc.

transform! Macro

Creates a Transform struct with specified field values. This offers a more concise syntax for defining transforms compared to Transform::new().field(...).field(...).

Syntax

transform!{ field: value, ... }

Examples

use osui::prelude::*;

// Creates a Transform with x=10, y=20, and default values for others.
let t1 = transform!{ x: 10, y: 20 };

// Creates a Transform centered horizontally, with full width and 2 units of padding.
let t2 = transform!{ x: Center, width: Full, padding: (2, 2) };

rsx! {
@transform!{ x: 5, y: 5, dimensions: (30, 10) };
Div { "A fixed-size div at (5,5)" }
}

How it Works

The macro expands to a Transform::new() call followed by setting each specified field using its into() method (which allows u16 to convert to Position::Const or Dimension::Const).

rsx! Macro

The primary macro for declaratively building UI element trees in OSUI. It supports static elements, dynamic elements with state dependencies, and component attachment.

Syntax (Simplified Key Patterns)

rsx! {
// Text literal
"Some text"

// Text literal with color
("Some colored text", 0xFF0000)

// Static element: `static` keyword
// ElementType, prop1: val1, ... { children }
static MyElement, some_prop: true, { "Static child" }

// Static element (no properties):
static MyElement { "Static child" }

// Dynamic element (default, no `static` keyword):
// %dependency1 %dependency2 ... ElementType, prop1: val1, ... { children }
%my_state
MyDynamicElement, another_prop: "value", { "Dynamic child: {my_state}" }

// Dynamic element (no properties):
%my_state
MyDynamicElement { "Dynamic child: {my_state}" }

// Attaching components: `@ComponentType;`
@Transform::new().center();
@Style { background: Background::Solid(0x111111) };
Div { "Div with Transform and Style" }

// Expanding another Rsx block: `function_call => (args)`
my_sub_rsx_function => (arg1, arg2)
}

How it Works (High-Level)

The rsx! macro recursively calls an internal rsx_inner! macro. It parses the declarative syntax and constructs a tree of RsxElement enums (either RsxElement::Element for static or RsxElement::DynElement for dynamic widgets). This Rsx tree is then used by Rsx::draw or Rsx::draw_parent to create the actual Widget instances on the Screen.

  • static: Creates a StaticWidget for the root Element.
  • No static: Creates a DynWidget whose element-creation closure will be re-evaluated on dependency changes.
  • %dependency: Automatically clones the Arc<State<T>> (or other DependencyHandler) and registers it with the DynWidget.
  • @Component: Attaches the specified component to the Widget being created.
  • properties: value: Sets public fields on the Element struct during its construction.
  • { children }: Recursively processes nested rsx! blocks to create child elements.

The rsx! macro is the most idiomatic way to build user interfaces in OSUI, offering a concise and powerful syntax for defining complex UI hierarchies and reactivity.