use super::memo::Memo;
use super::prop::Prop;
use super::signal::{RwSignal, Signal, create_derived, create_stored};
#[doc(hidden)]
pub struct ValueMarker;
#[doc(hidden)]
pub struct LossyMarker;
#[doc(hidden)]
pub struct ClosureMarker;
#[doc(hidden)]
pub struct SignalMarker;
#[doc(hidden)]
pub struct ConvertedSignalMarker;
#[doc(hidden)]
pub struct RwSignalMarker;
#[doc(hidden)]
pub struct MemoMarker;
pub trait IntoSignal<T: Clone + 'static, M = ValueMarker> {
fn into_signal(self) -> Signal<T>;
fn into_prop(self) -> Prop<T>
where
Self: Sized,
{
Prop::Reactive(self.into_signal())
}
}
pub trait IntoVal<T> {
fn into_val(self) -> T;
}
impl<T> IntoVal<T> for T {
fn into_val(self) -> T {
self
}
}
impl<T> IntoVal<Option<T>> for T {
fn into_val(self) -> Option<T> {
Some(self)
}
}
impl<T: Clone + 'static, I: Into<T>> IntoSignal<T, ValueMarker> for I {
fn into_signal(self) -> Signal<T> {
create_stored(self.into())
}
fn into_prop(self) -> Prop<T> {
Prop::Const(self.into())
}
}
impl<T, R, F> IntoSignal<T, ClosureMarker> for F
where
T: Clone + 'static,
R: IntoVal<T> + 'static,
F: Fn() -> R + 'static,
{
fn into_signal(self) -> Signal<T> {
create_derived(move || self().into_val())
}
}
macro_rules! converts {
(@pair $from:ty, $to:ty, |$value:ident| $conversion:expr) => {
impl $crate::reactive::IntoVal<$to> for $from {
fn into_val(self) -> $to {
let $value = self;
$conversion
}
}
impl $crate::reactive::IntoSignal<$to, $crate::reactive::ConvertedSignalMarker>
for $crate::reactive::Signal<$from>
{
fn into_signal(self) -> $crate::reactive::Signal<$to> {
$crate::reactive::create_derived(move || {
$crate::reactive::IntoVal::<$to>::into_val(self.get())
})
}
}
impl $crate::reactive::IntoSignal<$to, $crate::reactive::ConvertedSignalMarker>
for $crate::reactive::RwSignal<$from>
{
fn into_signal(self) -> $crate::reactive::Signal<$to> {
let read = self.read_only();
$crate::reactive::create_derived(move || {
$crate::reactive::IntoVal::<$to>::into_val(read.get())
})
}
}
impl $crate::reactive::IntoSignal<$to, $crate::reactive::ConvertedSignalMarker>
for $crate::reactive::Memo<$from>
{
fn into_signal(self) -> $crate::reactive::Signal<$to> {
$crate::reactive::create_derived(move || {
$crate::reactive::IntoVal::<$to>::into_val(self.get())
})
}
}
};
($($from:ty => $to:ty),* $(,)?) => {$(
$crate::reactive::converts!(@pair $from, $to, |value| <$to>::from(value));
)*};
($($from:ty as $to:ty),* $(,)?) => {$(
$crate::reactive::converts!(@pair $from, $to, |value| value as $to);
impl $crate::reactive::IntoSignal<$to, $crate::reactive::LossyMarker> for $from {
fn into_signal(self) -> $crate::reactive::Signal<$to> {
$crate::reactive::create_stored(self as $to)
}
fn into_prop(self) -> $crate::reactive::Prop<$to> {
$crate::reactive::Prop::Const(self as $to)
}
}
)*};
}
pub(crate) use converts;
converts!(f64 as f32, i32 as f32, u32 as f32);
converts!(u16 => f32);
impl<T: Clone + 'static> IntoSignal<Option<T>, ConvertedSignalMarker> for Signal<T> {
fn into_signal(self) -> Signal<Option<T>> {
create_derived(move || Some(self.get()))
}
}
impl<T: Clone + 'static> IntoSignal<Option<T>, ConvertedSignalMarker> for RwSignal<T> {
fn into_signal(self) -> Signal<Option<T>> {
let read = self.read_only();
create_derived(move || Some(read.get()))
}
}
impl<T: Clone + PartialEq + Send + 'static> IntoSignal<Option<T>, ConvertedSignalMarker>
for Memo<T>
{
fn into_signal(self) -> Signal<Option<T>> {
create_derived(move || Some(self.get()))
}
}
impl<T: Clone + 'static> IntoSignal<T, SignalMarker> for Signal<T> {
fn into_signal(self) -> Signal<T> {
self
}
}
impl<T: Clone + 'static> IntoSignal<T, RwSignalMarker> for RwSignal<T> {
fn into_signal(self) -> Signal<T> {
self.read_only()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::reactive::signal::create_signal;
#[test]
fn test_into_signal_for_string() {
let sig: Signal<String> = "hello".into_signal();
assert_eq!(sig.get(), "hello");
let sig: Signal<String> = String::from("world").into_signal();
assert_eq!(sig.get(), "world");
}
#[test]
fn test_into_signal_for_f32() {
let sig: Signal<f32> = 2.5f32.into_signal();
assert_eq!(sig.get(), 2.5);
}
#[test]
fn test_into_signal_for_f64_literal() {
let sig: Signal<f32> = 2.5.into_signal();
assert_eq!(sig.get(), 2.5);
}
#[test]
fn test_into_signal_for_f64_runtime_value() {
let value: f64 = 2.5;
let sig: Signal<f32> = value.into_signal();
assert_eq!(sig.get(), 2.5);
}
#[test]
fn test_into_signal_f64_narrows_to_f32() {
let sig: Signal<f32> = 0.1_f64.into_signal();
assert_eq!(sig.get(), 0.1_f32);
}
#[test]
fn test_into_signal_for_bool() {
let sig: Signal<bool> = true.into_signal();
assert!(sig.get());
let sig: Signal<bool> = false.into_signal();
assert!(!sig.get());
}
#[test]
fn test_into_signal_for_closures() {
let signal = create_signal(10);
let sig: Signal<i32> = (move || signal.get()).into_signal();
assert_eq!(sig.get(), 10);
signal.set(20);
assert_eq!(sig.get(), 20);
}
#[test]
fn test_signal_into_signal() {
let rw = create_signal(42);
let sig: Signal<i32> = rw.into_signal();
assert_eq!(sig.get(), 42);
rw.set(100);
assert_eq!(sig.get(), 100);
}
#[test]
fn test_rw_signal_into_signal() {
let rw = create_signal(42);
let sig: Signal<i32> = rw.into_signal();
assert_eq!(sig.get(), 42);
}
#[test]
fn test_closure_lossy_conversion() {
let sig: Signal<f32> = (|| 8i32).into_signal();
assert_eq!(sig.get(), 8.0);
}
#[test]
fn test_closure_f64_conversion() {
let sig: Signal<f32> = (|| 8.5).into_signal();
assert_eq!(sig.get(), 8.5);
}
#[test]
fn test_stored_is_copy() {
let sig = create_stored(42);
let sig2 = sig; assert_eq!(sig.get(), 42);
assert_eq!(sig2.get(), 42);
}
#[test]
fn test_derived_is_copy() {
let count = create_signal(5);
let derived = create_derived(move || count.get() * 2);
let derived2 = derived; assert_eq!(derived.get(), 10);
assert_eq!(derived2.get(), 10);
count.set(10);
assert_eq!(derived.get(), 20);
}
}
#[cfg(test)]
mod one_declaration_covers_every_form {
use super::*;
use crate::reactive::{create_memo, create_signal};
#[derive(Clone, Copy, PartialEq, Debug)]
struct Millimetres(f32);
impl From<f32> for Millimetres {
fn from(size: f32) -> Self {
Self(size)
}
}
converts!(f32 => Millimetres);
fn resolve<M>(source: impl IntoSignal<Millimetres, M>) -> Millimetres {
source.into_signal().get()
}
fn keep<M>(source: impl IntoSignal<Millimetres, M>) -> Prop<Millimetres> {
source.into_prop()
}
#[test]
fn a_pair_declared_once_arrives_in_every_form() {
let count = create_signal(4.0f32);
let read = count.read_only();
let doubled = create_memo(move || count.get() * 2.0);
assert_eq!(resolve(4.0f32), Millimetres(4.0), "a value");
assert_eq!(resolve(move || 4.0f32), Millimetres(4.0), "a closure");
assert_eq!(resolve(read), Millimetres(4.0), "a signal");
assert_eq!(resolve(count), Millimetres(4.0), "a writable signal");
assert_eq!(resolve(doubled), Millimetres(8.0), "a memo");
}
#[test]
fn and_a_value_is_kept_as_a_value() {
let count = create_signal(4.0f32);
let read = count.read_only();
let doubled = create_memo(move || count.get() * 2.0);
assert!(
matches!(keep(4.0f32), Prop::Const(Millimetres(4.0))),
"a value"
);
assert!(
matches!(keep(move || 4.0f32), Prop::Reactive(_)),
"a closure"
);
assert!(matches!(keep(read), Prop::Reactive(_)), "a signal");
assert!(
matches!(keep(count), Prop::Reactive(_)),
"a writable signal"
);
assert!(matches!(keep(doubled), Prop::Reactive(_)), "a memo");
}
mod a_widening_std_cannot_do {
use super::*;
converts!(i64 as f32);
fn resolve<M>(source: impl IntoSignal<f32, M>) -> f32 {
source.into_signal().get()
}
fn keep<M>(source: impl IntoSignal<f32, M>) -> Prop<f32> {
source.into_prop()
}
#[test]
fn arrives_in_every_form_too() {
let count = create_signal(4i64);
let read = count.read_only();
let doubled = create_memo(move || count.get() * 2);
assert_eq!(resolve(4i64), 4.0, "a value");
assert_eq!(resolve(move || 4i64), 4.0, "a closure");
assert_eq!(resolve(read), 4.0, "a signal");
assert_eq!(resolve(count), 4.0, "a writable signal");
assert_eq!(resolve(doubled), 8.0, "a memo");
}
#[test]
fn and_the_widened_value_is_kept_as_a_value() {
let count = create_signal(4i64);
let read = count.read_only();
let doubled = create_memo(move || count.get() * 2);
assert!(matches!(keep(4i64), Prop::Const(4.0)), "a value");
assert!(matches!(keep(move || 4i64), Prop::Reactive(_)), "a closure");
assert!(matches!(keep(read), Prop::Reactive(_)), "a signal");
assert!(
matches!(keep(count), Prop::Reactive(_)),
"a writable signal"
);
assert!(matches!(keep(doubled), Prop::Reactive(_)), "a memo");
}
}
}