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 ofself
(e.g.,MyStruct
).$self
: The instance variable (e.g.,self
).$events
: The event source object (e.g., aHandler::new
call orwidget.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
Arc
s for each capture in the closure. - Using
Arc::downgrade
for weak references if the closure might outlive the originalArc
.
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 aStaticWidget
for the rootElement
.- No
static
: Creates aDynWidget
whose element-creation closure will be re-evaluated on dependency changes. %dependency
: Automatically clones theArc<State<T>>
(or otherDependencyHandler
) and registers it with theDynWidget
.@Component
: Attaches the specified component to theWidget
being created.properties: value
: Sets public fields on theElement
struct during its construction.{ children }
: Recursively processes nestedrsx!
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.