Last active
December 5, 2020 03:31
-
-
Save PsichiX/1b649b726049a0839c4e53e46b1c6f05 to your computer and use it in GitHub Desktop.
RAUI data oriented API (declarative UI)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#[test] | |
fn test_hello_world() { | |
// convenient macro that produces widget component processing function. | |
widget_component! { | |
// <component name> ( [list of context data to unpack into scope] ) | |
app(key, named_slots) { | |
// easy way to get widgets from named slots. | |
unpack_named_slots!(named_slots => { title, content }); | |
// we always return new widgets tree. | |
widget! { | |
// Forgive me the syntax, i'll make a JSX-like one soon using procedural macros. | |
// `#{key}` - provided value gives a unique name to node. keys allows widgets | |
// to save state between render calls. here we just pass key of this widget. | |
// `vertical_box` - name of widget component to use. | |
// `[...]` - listed widget slots. here we just put previously unpacked named slots. | |
(#{key} vertical_box [ | |
{title} | |
{content} | |
]) | |
} | |
} | |
} | |
widget_component! { | |
button(key, props, unmounter, phase) { | |
println!("=== PROCESS BUTTON: {} | PHASE: {:?}", key, phase); | |
// buttons use string as props data. | |
let label = props.read_cloned_or_default::<String>(); | |
let k = key.to_string(); | |
// you use unmounter for storing closures that will be called when widget will be | |
// unmounted from the widget tree. | |
// closure provides arguments such as: | |
// - widget id | |
// - widget state | |
// - message sender (this one is used to message other widgets you know about) | |
// - signal sender (this one is used to message application host) | |
unmounter.listen(move |_, _, _, _| { | |
println!("=== BUTTON UNMOUNTED: {}", k); | |
}); | |
widget!{ | |
(#{key} text: {label}) | |
} | |
} | |
} | |
widget_component! { | |
title_bar(key, props) { | |
let title = props.read_cloned_or_default::<String>(); | |
widget! { | |
(#{key} text: {title}) | |
} | |
} | |
} | |
widget_component! { | |
vertical_box(key, listed_slots) { | |
// listed slots are just widget node children. | |
// here we just unwrap widget units (final atomic UI elements that renderers read). | |
let items = listed_slots | |
.into_iter() | |
.map(|slot| ListBoxItem { | |
slot: slot.try_into().expect("Cannot convert slot to WidgetUnit!"), | |
..Default::default() | |
}) | |
.collect::<Vec<_>>(); | |
// we use `{{{ ... }}}` to inform macro that this is widget unit. | |
widget! {{{ | |
ListBox { | |
items, | |
..Default::default() | |
} | |
}}} | |
} | |
} | |
widget_component! { | |
text(key, props) { | |
let text = props.read_cloned_or_default::<String>(); | |
widget!{{{ | |
TextBox { | |
text, | |
..Default::default() | |
} | |
}}} | |
} | |
} | |
let mut application = Application::new(); | |
let tree = widget! { | |
(app { | |
// <named slot name> = ( <widget to put in a slot> ) | |
title = (title_bar: {"Hello".to_owned()}) | |
content = (vertical_box [ | |
(#{"hi"} button: {"Say hi!".to_owned()}) | |
(#{"exit"} button: {"Close".to_owned()}) | |
]) | |
}) | |
}; | |
println!("=== INPUT:\n{:#?}", tree); | |
// some dummy widget tree renderer. | |
// it reads widget unit tree and transforms it into target format. | |
let mut renderer = HtmlRenderer::default(); | |
println!("=== PROCESS"); | |
// `apply()` sets new widget tree. | |
application.apply(tree); | |
// `render()` calls renderer to perform transformations on processed application widget tree. | |
if let Ok(output) = application.render(&mut renderer) { | |
println!("=== OUTPUT:\n{}", output); | |
} | |
println!("=== PROCESS"); | |
// by default application won't process widget tree if nothing was changed. | |
// "change" is either any widget state change, or new message sent to any widget (messages | |
// can be sent from application host, for example a mouse click, or from another widget). | |
application.forced_process(); | |
if let Ok(output) = application.render(&mut renderer) { | |
println!("=== OUTPUT:\n{}", output); | |
} | |
let tree = widget! { | |
(app) | |
}; | |
println!("=== INPUT:\n{:#?}", tree); | |
println!("=== PROCESS"); | |
application.apply(tree); | |
if let Ok(output) = application.render(&mut HtmlRenderer::default()) { | |
println!("=== OUTPUT:\n{}", output); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
=== INPUT: | |
Component( | |
WidgetComponent { | |
type_name: "app", | |
named_slots: { | |
"title": Component( | |
WidgetComponent { | |
type_name: "title_bar", | |
}, | |
), | |
"content": Component( | |
WidgetComponent { | |
type_name: "vertical_box", | |
listed_slots: [ | |
Component( | |
WidgetComponent { | |
type_name: "button", | |
key: "hi", | |
}, | |
), | |
Component( | |
WidgetComponent { | |
type_name: "button", | |
key: "exit", | |
}, | |
), | |
], | |
}, | |
), | |
}, | |
}, | |
) | |
=== PROCESS | |
=== PROCESS BUTTON: hi | PHASE: Mount | |
=== PROCESS BUTTON: exit | PHASE: Mount | |
=== OUTPUT: | |
<!DOCTYPE html> | |
<html dir="ltr" lang="en"> | |
<head > | |
<meta charset="utf-8"> | |
</head> | |
<body > | |
<div > | |
<span > | |
Hello | |
</span> | |
<div > | |
<span > | |
Say hi! | |
</span> | |
<span > | |
Close | |
</span> | |
</div> | |
</div> | |
</body> | |
</html> | |
=== PROCESS | |
=== PROCESS BUTTON: hi | PHASE: Update | |
=== PROCESS BUTTON: exit | PHASE: Update | |
=== OUTPUT: | |
<!DOCTYPE html> | |
<html lang="en" dir="ltr"> | |
<head > | |
<meta charset="utf-8"> | |
</head> | |
<body > | |
<div > | |
<span > | |
Hello | |
</span> | |
<div > | |
<span > | |
Say hi! | |
</span> | |
<span > | |
Close | |
</span> | |
</div> | |
</div> | |
</body> | |
</html> | |
=== INPUT: | |
Component( | |
WidgetComponent { | |
type_name: "app", | |
}, | |
) | |
=== PROCESS | |
=== BUTTON UNMOUNTED: hi | |
=== BUTTON UNMOUNTED: exit | |
=== OUTPUT: | |
<!DOCTYPE html> | |
<html dir="ltr" lang="en"> | |
<head > | |
<meta charset="utf-8"> | |
</head> | |
<body > | |
<div > | |
</div> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment