rinch-core
Core types and traits for rinch, including elements, reactive primitives, the DOM abstraction layer, hooks, and the Component trait.
Element Types
Element
The fundamental building block enum. Note that it is minimal - shell-level constructs (windows, menus, themes) are handled at the runtime level, not as Element variants.
#![allow(unused)]
fn main() {
pub enum Element {
/// Raw HTML content rendered by the DOM backend.
Html(String),
/// A fragment containing multiple children.
Fragment(Children),
/// A custom component implementing the Component trait.
Component(Rc<dyn Component>, Children),
}
}
WindowProps
Configuration for a window (used at the runtime level via run_with_window_props):
#![allow(unused)]
fn main() {
pub struct WindowProps {
pub title: String,
pub width: u32,
pub height: u32,
pub x: Option<i32>,
pub y: Option<i32>,
pub borderless: bool,
pub resizable: bool,
pub transparent: bool,
pub always_on_top: bool,
pub visible: bool,
pub resize_inset: Option<f32>,
}
}
DOM Abstraction Layer
The fine-grained reactive rendering system is built on three core types that abstract DOM operations away from any specific backend (desktop via Taffy/Parley/Vello, or browser-native via web_sys).
DomDocument Trait
The backend abstraction. All DOM operations go through this trait:
#![allow(unused)]
fn main() {
pub trait DomDocument {
fn create_element(&self, tag: &str) -> NodeId;
fn create_text(&self, content: &str) -> NodeId;
fn set_attribute(&self, node: NodeId, name: &str, value: &str);
fn remove_attribute(&self, node: NodeId, name: &str);
fn set_text(&self, node: NodeId, content: &str);
fn append_child(&self, parent: NodeId, child: NodeId);
fn insert_before(&self, parent: NodeId, child: NodeId, reference: NodeId);
fn remove_child(&self, parent: NodeId, child: NodeId);
fn register_handler(&self, handler: Box<dyn Fn()>) -> HandlerId;
// ... and more
}
}
RenderScope
Context for building DOM trees. Wraps a DomDocument and provides the API that the rsx! macro calls:
#![allow(unused)]
fn main() {
impl RenderScope {
pub fn create_element(&mut self, tag: &str) -> NodeHandle;
pub fn create_text(&mut self, content: &str) -> NodeHandle;
pub fn register_handler(&mut self, handler: impl Fn() + 'static) -> HandlerId;
}
}
Component functions receive a RenderScope (injected automatically by #[component]):
#![allow(unused)]
fn main() {
#[component]
fn my_component() -> NodeHandle {
rsx! { div { "Hello" } }
}
// Expands to: fn my_component(__scope: &mut RenderScope) -> NodeHandle { ... }
}
NodeHandle
A stable reference to a DOM node. Delegates all operations via Weak<RefCell<dyn DomDocument>>:
#![allow(unused)]
fn main() {
impl NodeHandle {
pub fn set_attribute(&self, name: &str, value: &str);
pub fn remove_attribute(&self, name: &str);
pub fn set_text(&self, content: &str);
pub fn append_child(&self, child: &NodeHandle);
pub fn insert_before(&self, child: &NodeHandle, reference: &NodeHandle);
pub fn remove_child(&self, child: &NodeHandle);
}
}
NodeHandles are used by Effects for surgical DOM updates:
#![allow(unused)]
fn main() {
// Signal change -> Effect runs -> NodeHandle.set_text() -> Minimal re-layout
}
Component Trait
Components implement the Component trait to render directly to DOM nodes:
#![allow(unused)]
fn main() {
pub trait Component: std::fmt::Debug + 'static {
fn render(&self, scope: &mut RenderScope, children: &[NodeHandle]) -> NodeHandle;
}
}
Reactive Module
Signal<T>
A reactive container for mutable state. Create signals directly with Signal::new(value).
#![allow(unused)]
fn main() {
impl<T> Signal<T> {
pub fn new(value: T) -> Self;
pub fn set(&self, value: T);
pub fn update(&self, f: impl FnOnce(&mut T));
pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R;
}
impl<T: Clone> Signal<T> {
pub fn get(&self) -> T;
}
}
Preferred usage via hooks:
#![allow(unused)]
fn main() {
#[component]
fn counter() -> NodeHandle {
let count = Signal::new(0);
rsx! {
p { {|| count.get().to_string()} }
button { onclick: move || count.update(|n| *n += 1), "+" }
}
}
}
Effect
A side-effect that tracks signal dependencies and re-runs when they change.
#![allow(unused)]
fn main() {
impl Effect {
pub fn new<F: FnMut() + 'static>(f: F) -> Self;
pub fn new_deferred<F: FnMut() + 'static>(f: F) -> Self;
pub fn run(&self);
pub fn dispose(&self);
}
}
In practice, Effects are created automatically by the rsx! macro for reactive expressions ({|| expr}), and by Effect::new() for explicit side effects.
Memo<T>
A cached computed value that automatically re-computes when its dependencies change.
#![allow(unused)]
fn main() {
impl<T: Clone + 'static> Memo<T> {
pub fn new<F: Fn() -> T + 'static>(f: F) -> Self;
pub fn get(&self) -> T;
}
}
Scope
Manages the lifetime of reactive primitives.
#![allow(unused)]
fn main() {
impl Scope {
pub fn new() -> Self;
pub fn run<R>(&self, f: impl FnOnce() -> R) -> R;
pub fn add_effect(&self, effect: Effect);
pub fn dispose(&self);
}
}
Reactive Primitives
| Primitive | Purpose |
|---|---|
Signal::new(value) | Reactive state container |
Effect::new(closure) | Side effects with auto-tracked dependencies |
Memo::new(closure) | Cached computed values |
create_context(value) | Create shared context |
use_context::<T>() | Access shared context (panics if missing) |
try_use_context::<T>() | Access shared context (returns Option) |
Utility Functions
batch
Batch multiple signal updates to defer effect execution:
#![allow(unused)]
fn main() {
pub fn batch<R>(f: impl FnOnce() -> R) -> R;
}
derived
Create a memo (convenience function):
#![allow(unused)]
fn main() {
pub fn derived<T: Clone + 'static>(f: impl Fn() -> T + 'static) -> Memo<T>;
}
untracked
Read signals without tracking dependencies:
#![allow(unused)]
fn main() {
pub fn untracked<R>(f: impl FnOnce() -> R) -> R;
}
Control Flow
show_dom / Show
Reactive conditional rendering. Swaps DOM content when the condition changes:
#![allow(unused)]
fn main() {
#[component]
fn example() -> NodeHandle {
let visible = Signal::new(true);
rsx! {
Show {
when: {move || visible.get()},
div { "Visible!" }
}
}
}
}
for_each_dom / For
Keyed list rendering with minimal DOM operations via LIS-based reconciliation:
#![allow(unused)]
fn main() {
#[component]
fn example() -> NodeHandle {
let items = Signal::new(vec!["a", "b", "c"]);
rsx! {
For {
each: {move || items.get().into_iter().map(|s| ForItem::new(s, s)).collect()},
|item: &ForItem| rsx! { div { {item.downcast::<&str>().unwrap()} } }
}
}
}
}