#![allow(clippy::type_complexity)]
use std::any::Any;
use std::cell::RefCell;
use std::collections::HashMap;
use std::marker::PhantomData;
use std::rc::Rc;
pub trait Lifecycle {
fn on_init(&mut self) {}
fn on_changes(&mut self, _changes: &Changes) {}
fn on_view_init(&mut self) {}
fn on_check(&mut self) {}
fn on_destroy(&mut self) {}
}
#[derive(Debug, Default)]
pub struct Changes {
changed: HashMap<String, (Box<dyn Any>, Box<dyn Any>)>,
}
impl Changes {
pub fn new() -> Self {
Self::default()
}
pub fn has(&self, name: &str) -> bool {
self.changed.contains_key(name)
}
pub fn get<T: 'static + Clone>(&self, name: &str) -> Option<(T, T)> {
self.changed.get(name).and_then(|(prev, curr)| {
let prev = prev.downcast_ref::<T>()?;
let curr = curr.downcast_ref::<T>()?;
Some((prev.clone(), curr.clone()))
})
}
pub fn is_first_change(&self, name: &str) -> bool {
self.changed.get(name).map(|(p, _)| p.is::<()>()).unwrap_or(false)
}
}
#[derive(Clone)]
pub struct State<T> {
inner: Rc<RefCell<T>>,
subscribers: Rc<RefCell<Vec<Box<dyn Fn(&T)>>>>,
}
impl<T: 'static> State<T> {
pub fn new(value: T) -> Self {
Self {
inner: Rc::new(RefCell::new(value)),
subscribers: Rc::new(RefCell::new(Vec::new())),
}
}
pub fn get(&self) -> std::cell::Ref<'_, T> {
self.inner.borrow()
}
pub fn set(&self, value: T) {
*self.inner.borrow_mut() = value;
self.notify();
}
pub fn update<F: FnOnce(&mut T)>(&self, f: F) {
f(&mut self.inner.borrow_mut());
self.notify();
}
pub fn subscribe<F: Fn(&T) + 'static>(&self, f: F) {
self.subscribers.borrow_mut().push(Box::new(f));
}
fn notify(&self) {
let value = self.inner.borrow();
for subscriber in self.subscribers.borrow().iter() {
subscriber(&*value);
}
}
}
impl<T: Clone + 'static> State<T> {
pub fn value(&self) -> T {
self.inner.borrow().clone()
}
}
impl<T: Default + 'static> Default for State<T> {
fn default() -> Self {
Self::new(T::default())
}
}
#[derive(Clone)]
pub struct EventEmitter<T> {
handlers: Rc<RefCell<Vec<Box<dyn Fn(T)>>>>,
}
impl<T: Clone + 'static> EventEmitter<T> {
pub fn new() -> Self {
Self {
handlers: Rc::new(RefCell::new(Vec::new())),
}
}
pub fn emit(&self, value: T) {
for handler in self.handlers.borrow().iter() {
handler(value.clone());
}
}
pub fn subscribe<F: Fn(T) + 'static>(&self, handler: F) {
self.handlers.borrow_mut().push(Box::new(handler));
}
}
impl<T: Clone + 'static> Default for EventEmitter<T> {
fn default() -> Self {
Self::new()
}
}
pub struct ComponentContext<S> {
state: State<S>,
should_update: Rc<RefCell<bool>>,
}
impl<S: 'static> ComponentContext<S> {
pub fn new(state: State<S>) -> Self {
Self {
state,
should_update: Rc::new(RefCell::new(false)),
}
}
pub fn state(&self) -> std::cell::Ref<'_, S> {
self.state.get()
}
pub fn update<F: FnOnce(&mut S)>(&self, f: F) {
self.state.update(f);
*self.should_update.borrow_mut() = true;
}
pub fn set(&self, value: S) {
self.state.set(value);
*self.should_update.borrow_mut() = true;
}
}
impl<S: Clone + 'static> ComponentContext<S> {
pub fn value(&self) -> S {
self.state.value()
}
}
pub struct ComponentBuilder<S, W> {
name: String,
state: Option<S>,
template: Option<Box<dyn Fn(&S, &ComponentContext<S>) -> W>>,
styles: Vec<String>,
on_init: Option<Box<dyn Fn(&S)>>,
on_destroy: Option<Box<dyn Fn(&S)>>,
on_changes: Option<Box<dyn Fn(&S, &Changes)>>,
inputs: HashMap<String, Box<dyn Any>>,
_marker: PhantomData<W>,
}
impl<S: Default + 'static, W: 'static> ComponentBuilder<S, W> {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
state: None,
template: None,
styles: Vec::new(),
on_init: None,
on_destroy: None,
on_changes: None,
inputs: HashMap::new(),
_marker: PhantomData,
}
}
}
impl<S: 'static, W: 'static> ComponentBuilder<S, W> {
pub fn state(mut self, state: S) -> Self {
self.state = Some(state);
self
}
pub fn template<F>(mut self, f: F) -> Self
where
F: Fn(&S, &ComponentContext<S>) -> W + 'static,
{
self.template = Some(Box::new(f));
self
}
pub fn styles(mut self, css: impl Into<String>) -> Self {
self.styles.push(css.into());
self
}
pub fn on_init<F: Fn(&S) + 'static>(mut self, f: F) -> Self {
self.on_init = Some(Box::new(f));
self
}
pub fn on_destroy<F: Fn(&S) + 'static>(mut self, f: F) -> Self {
self.on_destroy = Some(Box::new(f));
self
}
pub fn on_changes<F: Fn(&S, &Changes) + 'static>(mut self, f: F) -> Self {
self.on_changes = Some(Box::new(f));
self
}
pub fn input<T: 'static>(mut self, name: &str, default: T) -> Self {
self.inputs.insert(name.to_string(), Box::new(default));
self
}
}
impl<S: Clone + 'static, W: crate::widget::Widget + 'static> ComponentBuilder<S, W> {
pub fn build(self) -> BuiltComponent<S, W> {
let state = State::new(self.state.expect("Component requires state"));
if let Some(on_init) = &self.on_init {
on_init(&state.get());
}
BuiltComponent {
name: self.name,
state,
template: self.template.expect("Component requires template"),
styles: self.styles,
on_destroy: self.on_destroy,
_marker: PhantomData,
}
}
}
pub struct BuiltComponent<S: 'static, W> {
name: String,
state: State<S>,
template: Box<dyn Fn(&S, &ComponentContext<S>) -> W>,
styles: Vec<String>,
on_destroy: Option<Box<dyn Fn(&S)>>,
_marker: PhantomData<W>,
}
impl<S: Clone + 'static, W: crate::widget::Widget + 'static> BuiltComponent<S, W> {
pub fn render(&self) -> W {
let ctx = ComponentContext::new(self.state.clone());
(self.template)(&self.state.get(), &ctx)
}
pub fn name(&self) -> &str {
&self.name
}
pub fn styles(&self) -> &[String] {
&self.styles
}
}
impl<S: 'static, W> Drop for BuiltComponent<S, W> {
fn drop(&mut self) {
if let Some(on_destroy) = &self.on_destroy {
on_destroy(&self.state.get());
}
}
}
pub fn component<S: Default + 'static, W: 'static>(name: &str) -> ComponentBuilder<S, W> {
ComponentBuilder::new(name)
}
pub struct If<W> {
condition: bool,
then_widget: Option<W>,
else_widget: Option<W>,
}
impl<W> If<W> {
pub fn new(condition: bool) -> Self {
Self {
condition,
then_widget: None,
else_widget: None,
}
}
pub fn then(mut self, widget: W) -> Self {
self.then_widget = Some(widget);
self
}
pub fn otherwise(mut self, widget: W) -> Self {
self.else_widget = Some(widget);
self
}
pub fn render(self) -> Option<W> {
if self.condition {
self.then_widget
} else {
self.else_widget
}
}
}
pub struct For<T, W, F>
where
F: Fn(T, usize) -> W,
{
items: Vec<T>,
template: F,
_marker: PhantomData<W>,
}
impl<T, W, F> For<T, W, F>
where
F: Fn(T, usize) -> W,
{
pub fn each(items: impl IntoIterator<Item = T>, template: F) -> Self {
Self {
items: items.into_iter().collect(),
template,
_marker: PhantomData,
}
}
pub fn render(self) -> Vec<W> {
self.items
.into_iter()
.enumerate()
.map(|(i, item)| (self.template)(item, i))
.collect()
}
}
pub struct Switch<T, W> {
value: T,
cases: Vec<(T, W)>,
default: Option<W>,
}
impl<T: PartialEq, W> Switch<T, W> {
pub fn on(value: T) -> Self {
Self {
value,
cases: Vec::new(),
default: None,
}
}
pub fn case(mut self, match_value: T, widget: W) -> Self {
self.cases.push((match_value, widget));
self
}
pub fn default(mut self, widget: W) -> Self {
self.default = Some(widget);
self
}
pub fn render(self) -> Option<W> {
for (case_value, widget) in self.cases {
if case_value == self.value {
return Some(widget);
}
}
self.default
}
}
#[derive(Clone)]
pub struct Binding<T> {
value: Rc<RefCell<T>>,
on_change: Rc<RefCell<Option<Box<dyn Fn(&T)>>>>,
}
impl<T: 'static> Binding<T> {
pub fn new(value: T) -> Self {
Self {
value: Rc::new(RefCell::new(value)),
on_change: Rc::new(RefCell::new(None)),
}
}
pub fn get(&self) -> std::cell::Ref<'_, T> {
self.value.borrow()
}
pub fn set(&self, value: T) {
*self.value.borrow_mut() = value;
if let Some(callback) = self.on_change.borrow().as_ref() {
callback(&*self.value.borrow());
}
}
pub fn on_change<F: Fn(&T) + 'static>(&self, f: F) {
*self.on_change.borrow_mut() = Some(Box::new(f));
}
}
impl<T: Clone + 'static> Binding<T> {
pub fn value(&self) -> T {
self.value.borrow().clone()
}
}
impl<T: Default + 'static> Default for Binding<T> {
fn default() -> Self {
Self::new(T::default())
}
}
pub struct Model<T> {
binding: Binding<T>,
}
impl<T: 'static> Model<T> {
pub fn new(value: T) -> Self {
Self {
binding: Binding::new(value),
}
}
pub fn binding(&self) -> &Binding<T> {
&self.binding
}
pub fn get(&self) -> std::cell::Ref<'_, T> {
self.binding.get()
}
pub fn set(&self, value: T) {
self.binding.set(value);
}
}
impl<T: Clone + 'static> Model<T> {
pub fn value(&self) -> T {
self.binding.value()
}
}
impl<T: Default + 'static> Default for Model<T> {
fn default() -> Self {
Self::new(T::default())
}
}
pub trait Pipe<T, U> {
fn transform(&self, value: T) -> U;
}
pub struct UppercasePipe;
impl Pipe<&str, String> for UppercasePipe {
fn transform(&self, value: &str) -> String {
value.to_uppercase()
}
}
impl Pipe<String, String> for UppercasePipe {
fn transform(&self, value: String) -> String {
value.to_uppercase()
}
}
pub struct LowercasePipe;
impl Pipe<&str, String> for LowercasePipe {
fn transform(&self, value: &str) -> String {
value.to_lowercase()
}
}
pub struct CurrencyPipe {
pub symbol: &'static str,
pub decimals: usize,
}
impl Default for CurrencyPipe {
fn default() -> Self {
Self {
symbol: "$",
decimals: 2,
}
}
}
impl Pipe<f64, String> for CurrencyPipe {
fn transform(&self, value: f64) -> String {
format!("{}{:.decimals$}", self.symbol, value, decimals = self.decimals)
}
}
pub struct DatePipe {
pub format: &'static str,
}
impl Default for DatePipe {
fn default() -> Self {
Self { format: "%Y-%m-%d" }
}
}
#[macro_export]
macro_rules! define_component {
(
name: $name:expr,
state: { $($field:ident : $type:ty = $default:expr),* $(,)? },
template: $template:expr
$(, styles: $styles:expr)?
$(, on_init: $on_init:expr)?
$(, on_destroy: $on_destroy:expr)?
$(,)?
) => {{
#[derive(Clone, Default)]
struct State {
$($field: $type,)*
}
let initial_state = State {
$($field: $default,)*
};
let mut builder = $crate::component::component::<State, _>($name)
.state(initial_state)
.template($template);
$(
builder = builder.styles($styles);
)?
$(
builder = builder.on_init($on_init);
)?
$(
builder = builder.on_destroy($on_destroy);
)?
builder.build()
}};
}
#[macro_export]
macro_rules! ng_if {
($cond:expr, then: $then:expr $(, else: $else:expr)? $(,)?) => {{
let directive = $crate::component::If::new($cond).then($then);
$(
let directive = directive.otherwise($else);
)?
directive.render()
}};
}
#[macro_export]
macro_rules! ng_for {
($items:expr, |$item:ident| $template:expr) => {{
$crate::component::For::each($items, |$item, _| $template).render()
}};
($items:expr, |$item:ident, $index:ident| $template:expr) => {{
$crate::component::For::each($items, |$item, $index| $template).render()
}};
}
#[macro_export]
macro_rules! ng_switch {
($value:expr, $($case:pat => $widget:expr),+ $(, _ => $default:expr)? $(,)?) => {{
match $value {
$($case => Some($widget),)+
$(_ => Some($default),)?
#[allow(unreachable_patterns)]
_ => None,
}
}};
}
#[macro_export]
macro_rules! bind {
($widget:expr, $($prop:ident : $value:expr),* $(,)?) => {{
let mut widget = $widget;
$(
widget = widget.$prop($value);
)*
widget
}};
}
#[macro_export]
macro_rules! on {
($widget:expr, click: $handler:expr $(,)?) => {{
$widget.on_click($handler)
}};
($widget:expr, change: $handler:expr $(,)?) => {{
$widget.on_change($handler)
}};
($widget:expr, submit: $handler:expr $(,)?) => {{
$widget.on_submit($handler)
}};
}
#[macro_export]
macro_rules! model {
($widget:expr, $model:expr) => {{
let model = $model.clone();
$widget
.value($model.value())
.on_change(move |v| model.set(v.to_string()))
}};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_state() {
let state = State::new(42);
assert_eq!(*state.get(), 42);
state.set(100);
assert_eq!(*state.get(), 100);
state.update(|v| *v += 1);
assert_eq!(*state.get(), 101);
}
#[test]
fn test_event_emitter() {
let emitter = EventEmitter::<i32>::new();
let received = Rc::new(RefCell::new(0));
let received_clone = received.clone();
emitter.subscribe(move |v| {
*received_clone.borrow_mut() = v;
});
emitter.emit(42);
assert_eq!(*received.borrow(), 42);
}
#[test]
fn test_if_directive() {
let result = If::new(true)
.then("yes")
.otherwise("no")
.render();
assert_eq!(result, Some("yes"));
let result = If::new(false)
.then("yes")
.otherwise("no")
.render();
assert_eq!(result, Some("no"));
}
#[test]
fn test_for_directive() {
let items = vec!["a", "b", "c"];
let result = For::each(items, |item, i| format!("{}: {}", i, item)).render();
assert_eq!(result, vec!["0: a", "1: b", "2: c"]);
}
#[test]
fn test_switch_directive() {
let result = Switch::on("b")
.case("a", 1)
.case("b", 2)
.case("c", 3)
.default(0)
.render();
assert_eq!(result, Some(2));
}
#[test]
fn test_binding() {
let binding = Binding::new(10);
assert_eq!(*binding.get(), 10);
binding.set(20);
assert_eq!(*binding.get(), 20);
}
}