use std::rc::Rc;
use crate::signal::Signal;
#[derive(Clone)]
pub struct Ref<T>(Rc<Signal<T>>);
impl<T: Clone + 'static> Ref<T> {
#[inline]
pub fn new(value: T) -> Self {
Self(Rc::new(Signal::new(value)))
}
#[inline]
pub fn get(&self) -> T {
self.0.get()
}
#[inline]
pub fn set(&self, value: T) {
self.0.set(value);
}
#[inline]
pub fn update<F>(&self, f: F)
where
F: FnOnce(T) -> T,
{
self.0.update(f);
}
#[inline]
pub fn set_if_changed(&self, value: T)
where
T: PartialEq,
{
self.0.set_if_changed(value);
}
#[inline]
pub fn as_signal(&self) -> &Rc<Signal<T>> {
&self.0
}
#[inline]
pub fn into_signal(self) -> Rc<Signal<T>> {
self.0
}
}
impl<T: Clone + 'static> std::ops::Deref for Ref<T> {
type Target = Rc<Signal<T>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: std::fmt::Debug + Clone + 'static> std::fmt::Debug for Ref<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("Ref").field(&self.get()).finish()
}
}
#[inline]
pub fn ref_value<T: Clone + 'static>(value: T) -> Ref<T> {
Ref::new(value)
}
#[inline]
pub fn from_signal<T: Clone + 'static>(signal: Rc<Signal<T>>) -> Ref<T> {
Ref(signal)
}
#[inline]
pub fn cell_ref<T: Clone + 'static>(signal: Rc<Signal<T>>) -> Ref<T> {
from_signal(signal)
}
#[inline]
pub fn to_signal<T: Clone + 'static>(r: &Ref<T>) -> &Rc<Signal<T>> {
r.as_signal()
}
pub struct ShallowRef<T: Copy>(std::rc::Rc<std::cell::Cell<T>>);
impl<T: Copy> ShallowRef<T> {
#[inline]
pub fn new(value: T) -> Self {
Self(std::rc::Rc::new(std::cell::Cell::new(value)))
}
#[inline]
pub fn get(&self) -> T {
self.0.get()
}
#[inline]
pub fn set(&self, value: T) {
self.0.set(value);
}
#[inline]
pub fn update<F>(&self, f: F)
where
F: FnOnce(T) -> T,
{
self.0.set(f(self.0.get()));
}
}
impl<T: Copy> Clone for ShallowRef<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T: Copy + std::fmt::Debug> std::fmt::Debug for ShallowRef<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ShallowRef").field(&self.get()).finish()
}
}
#[inline]
pub fn shallow_ref<T: Copy>(value: T) -> ShallowRef<T> {
ShallowRef::new(value)
}
#[macro_export]
macro_rules! reactive_struct {
($( $field:ident: $ty:ty = $init:expr ),* $(,)?) => {
{
struct __State {
$( $field: $ty, )*
}
__State {
$( $field: $init, )*
}
}
};
}
#[macro_export]
macro_rules! reactive {
($( $field:ident: $ty:ty = $init:expr ),* $(,)?) => {
{
struct __ReactiveState {
$( $field: $crate::ergonomics::Ref<$ty>, )*
}
__ReactiveState {
$( $field: $crate::ergonomics::ref_value($init), )*
}
}
};
}
#[macro_export]
macro_rules! define_emits {
($( $event:ident ),* $(,)?) => {
pub const EMIT_EVENTS: &[&str] = &[$(stringify!($event),)*];
};
}
pub fn emit(_event: &str, _payload: &str) {
}
#[inline]
pub fn emit_void(event: &str) {
emit(event, "");
}
pub fn watch_ref<T: Clone + PartialEq + 'static>(
source: &Ref<T>,
callback: impl FnMut(T, T) + 'static,
) {
let sig = source.as_signal().clone();
crate::watch::watch(
move || sig.get(),
callback,
crate::watch::WatchOptions::default(),
);
}
pub fn watch_effect<F: FnMut() + 'static>(f: F) -> crate::signal::EffectHandle {
crate::signal::effect(f)
}
pub fn computed_ref<T: Clone + PartialEq + 'static, F: Fn() -> T + 'static>(compute: F) -> Ref<T> {
Ref(crate::signal::computed(compute))
}
#[inline]
pub fn readonly<T: Clone + PartialEq + 'static, F: Fn() -> T + 'static>(compute: F) -> Ref<T> {
computed_ref(compute)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ref_value() {
let count = ref_value(0);
assert_eq!(count.get(), 0);
count.set(42);
assert_eq!(count.get(), 42);
count.update(|v| v + 1);
assert_eq!(count.get(), 43);
}
#[test]
fn test_ref_clone() {
let a = ref_value(String::from("hello"));
let b = a.clone();
b.set(String::from("world"));
assert_eq!(a.get(), "world");
}
#[test]
fn test_ref_set_if_changed() {
let count = ref_value(0);
count.set(1);
count.set_if_changed(1); count.set_if_changed(2); assert_eq!(count.get(), 2);
}
#[test]
fn test_shallow_ref() {
let count = shallow_ref(0);
assert_eq!(count.get(), 0);
count.set(42);
assert_eq!(count.get(), 42);
count.update(|v| v * 2);
assert_eq!(count.get(), 84);
}
#[test]
fn test_shallow_ref_clone() {
let a = shallow_ref(10);
let b = a.clone();
b.set(20);
assert_eq!(a.get(), 20);
}
#[test]
fn test_reactive_macro() {
let state = reactive! {
count: i32 = 0,
name: String = String::from("hello"),
};
assert_eq!(state.count.get(), 0);
assert_eq!(state.name.get(), "hello");
state.count.set(42);
state.name.set(String::from("world"));
assert_eq!(state.count.get(), 42);
assert_eq!(state.name.get(), "world");
}
#[test]
#[allow(clippy::const_is_empty)]
fn test_define_emits_macro() {
define_emits!(change, reset);
assert!(!EMIT_EVENTS.is_empty());
}
#[test]
fn test_computed_ref() {
let count = ref_value(5);
let doubled = computed_ref({
let count = count.clone();
move || count.get() * 2
});
assert_eq!(doubled.get(), 10);
count.set(10);
assert_eq!(doubled.get(), 20);
}
#[test]
fn test_readonly() {
let count = ref_value(5);
let doubled = readonly({
let count = count.clone();
move || count.get() * 2
});
assert_eq!(doubled.get(), 10);
}
#[test]
fn test_from_signal() {
let signal = Rc::new(Signal::new(42));
let r = from_signal(signal);
assert_eq!(r.get(), 42);
r.set(100);
assert_eq!(r.get(), 100);
}
#[test]
fn test_into_signal() {
let r = ref_value(42);
let signal = r.into_signal();
assert_eq!(signal.get(), 42);
}
#[test]
fn test_deref() {
let r = ref_value(42);
assert_eq!(r.get(), 42);
}
#[test]
fn test_emit_void() {
emit_void("test");
}
#[test]
fn test_emit_with_payload() {
emit("test", "payload");
}
}