Renderer Agnostic User Interface system
This module gives you a way to make UI logic completely separated from rendering (still you have to implement rendering UI tree got from this module, or use some of already existing renderers - they will be added to list here when some renderer will appear).
Also: this project is highly inspired by React made by facebook team.
Contents
- Installation
- Usage
- Server
- Render event
- Signal event
- Client rect
- Focused node
- Component nodes
- Performing actions on components
- Processing UI tree
- Signaling actions
- Triggering events
- Components
Installation
Add dependency into project Cargo.toml
file:
[dependencies]
raui = "0.1"
Usage
Server
NOTE: Event callbacks are extern "C"
functions by design because RAUI basically serves as system that can connect application logic with host application renderer, still staying separated library to keep it independent from host.
Render event
extern fn on_render(buffer: *const u8, size: u32) {
}
fn main() {
let mut server = Server::new();
server.on_render_event = Some(on_render);
}
This callback called when RAUI re-renders UI tree. Tt happens when we call Server::process()
on invalid tree. Tree is invalid when one of it's nodes has changed internal state.
Signal event
extern fn on_signal(id: *const c_char) {
}
fn main() {
let mut server = Server::new();
server.on_signal_event = Some(on_signal);
}
This callback is called when some UI tree node triggers some signal. Signals are easiest way to communicate actions between RAUI server and host application. They can be used to tell host that we clicked some button (for now the do not pass any arguments, but later they will).
Client rect
fn main() {
let mut server = Server::new();
server.set_client_rect(
&Rect::from(
&Vec2::zero(),
&Vec2::from(1024.0, 768.0)
)
);
}
Client rect sets viewport for UI tree. In most cases you just want to match your application window size with no offset.
Focused node
fn main() {
let mut server = Server::new();
let node = server.set_root::<Container>();
server.set_focused(Some(node));
}
By default node is focused when user press mouse button on node with BehaviourFlags::BF_READING_MOUSE
behaviour:
component.set_behaviour(behaviour_flags::BF_READING_MOUSE);
but we can set focused node manually with Server::set_focused()
function.
Component nodes
Nodes cannot be created as standalone objects and then connected to server or other node - instead, they have to be created and automatically connected using server.
Create root node easy way:
fn main() {
let mut server = Server::new();
server.set_root::<Container>();
}
Create root node with action (usually this is the way to setup node on it's creation phase and create it's childs):
fn main() {
let mut server = Server::new();
server.set_root_with_action::<Container, _>(&mut |server, node, component| {
component.set_area(
&Rect::from(
&Vec2::zero(),
&Vec2::zero()
)
);
component.set_coords(components::container::CS_PARENT);
component.set_color(&Color::red());
});
}
Create child node easy way:
fn main() {
let mut server = Server::new();
server.set_root_with_action::<Container, _>(&mut |server, node, component| {
server.add_child::<Image>(node);
});
}
Create child node with action:
fn main() {
let mut server = Server::new();
server.set_root_with_action::<Container, _>(&mut |server, node, component| {
server.add_child_with_action::<Image, _>(node, &mut |server, node, component| {
component.set_area(
&Rect::from(
&Vec2::from(100.0, 50.0),
&Vec2::from(300.0, 150.0)
)
);
component.set_coords(components::container::CS_LOCAL);
component.set_image_source("logo".to_string());
component.set_behaviour(behaviour_flags::BF_READING_MOUSE);
});
});
}
If you want to remove node at some point you can do it by:
fn main() {
let mut server = Server::new();
let node = server.set_root::<Container>();
server.remove_component(node);
}
Performing actions on components
At some point you may want to modify your component (it's state). You can do it with Server::perform_action
functions family.
Performing action on node:
fn main() {
let mut server = Server::new();
let node = server.set_root::<Container>();
server.perform_action::<_>(node, &mut |component| {
component.set_behaviour(behaviour_flags::BF_READING_MOUSE);
});
}
Performing action on node children only:
fn main() {
let mut server = Server::new();
let mut subnode = ComponentNode::default();
server.set_root_with_action::<Container, _>(&mut |server, node, component| {
subnode = server.add_child::<Image>(node);
});
server.perform_action_on_children::<_>(subnode, &mut |component| {
component.set_behaviour(behaviour_flags::BF_READING_MOUSE);
});
}
Performing action on node and it's children:
fn main() {
let mut server = Server::new();
let mut subnode = ComponentNode::default();
server.set_root_with_action::<Container, _>(&mut |server, node, component| {
subnode = server.add_child::<Image>(node);
});
server.perform_action_on_children_too::<_>(subnode, &mut |component| {
component.set_behaviour(behaviour_flags::BF_READING_MOUSE);
});
}
Processing UI tree
fn main() {
let mut server = Server::new();
server.set_root_with_action::<Container, _>(&mut |server, node, component| {
server.add_child::<Image>(node);
});
server.process(false);
}
This function processes whole UI tree and triggers render event if tree is invalid (has changed state).
Signaling actions
TODO
Triggering events
With this functions, host application can communicate changes in UI environment (like mouse and keyboard interactions).
Triggering mouse events:
fn main() {
let mut server = Server::new();
server.set_root_with_action::<Container, _>(&mut |server, node, component| {
server.add_child_with_action::<Image, _>(node, &mut |server, node, component| {
component.set_area(
&Rect::from(
&Vec2::from(100.0, 50.0),
&Vec2::from(300.0, 150.0)
)
);
component.set_coords(components::container::CS_LOCAL);
component.set_image_source("logo".to_string());
component.set_behaviour(behaviour_flags::BF_READING_MOUSE);
component.base.on_click_event = Some(|| println!("Image clicked!"));
});
});
server.trigger_mouse_down(&Vec2::from(200.0, 100.0));
server.trigger_mouse_move(&Vec2::from(300.0, 100.0));
server.trigger_mouse_up(&Vec2::from(300.0, 100.0));
server.trigger_mouse_click(&Vec2::from(200.0, 100.0));
}
Triggering keyboard events:
fn main() {
let mut server = Server::new();
server.set_root_with_action::<Container, _>(&mut |server, node, component| {
server.add_child_with_action::<Image, _>(node, &mut |server, node, component| {
component.set_area(
&Rect::from(
&Vec2::from(100.0, 50.0),
&Vec2::from(300.0, 150.0)
)
);
component.set_coords(components::container::CS_LOCAL);
component.set_image_source("logo".to_string());
component.set_behaviour(behaviour_flags::BF_READING_MOUSE);
component.base.on_key_pressed_event = Some(
|code, _modifiers| println!("Pressed key on text: {}", code)
);
});
});
server.trigger_key_pressed(32, &key_modifiers::KM_NONE);
server.trigger_key_released(32, &key_modifiers::KM_NONE);
server.trigger_key_tap(32, &key_modifiers::KM_NONE);
}
Components
Components are logic of node. Each component should do one and only one thing, like: be a container that layout it's children; draw image; draw text. Construct UI tree using smallest components you can use and try to avoid big "inheritance"/composition chains of your custom components.
There are basic components provided by module:
- Container that can layout itself and it's children relative to some coordinate system;
- Image that can display image;
- Text that can display text.
If you want to make your custom component, you have to create struct that implement Component
and all it's functions (yeah, thanks to rust design, more your trait "mass" is, more you have to produce boilerplate code to implement it, but for now it's unavoidable pain in the a**). If you want your custom component to "inherit" Container
functionality, just composite it (you can see example of it here).