1use std::{fmt::Arguments, marker::PhantomData};
2
3use crate::innerlude::*;
4
5#[rustversion::attr(
40 since(1.78.0),
41 diagnostic::on_unimplemented(
42 message = "`Props` is not implemented for `{Self}`",
43 label = "Props",
44 note = "Props is a trait that is automatically implemented for all structs that can be used as props for a component",
45 note = "If you manually created a new properties struct, you may have forgotten to add `#[derive(Props, PartialEq, Clone)]` to your struct",
46 )
47)]
48pub trait Properties: Clone + Sized + 'static {
49 type ComponentBuilder<RenderFn, Marker>;
51
52 fn component_builder<RenderFn, Marker>(
54 render_fn: RenderFn,
55 ) -> Self::ComponentBuilder<RenderFn, Marker>;
56
57 fn memoize(&mut self, other: &Self) -> bool;
59
60 fn into_vcomponent<M: 'static>(self, render_fn: impl ComponentFunction<Self, M>) -> VComponent {
62 let type_name = std::any::type_name_of_val(&render_fn);
63 VComponent::new(render_fn, self, type_name)
64 }
65}
66
67impl Properties for () {
68 type ComponentBuilder<RenderFn, Marker> = ComponentBuilder<RenderFn, EmptyBuilder, (), Marker>;
69
70 fn component_builder<RenderFn, Marker>(
71 render_fn: RenderFn,
72 ) -> Self::ComponentBuilder<RenderFn, Marker> {
73 ComponentBuilder::new(render_fn, EmptyBuilder {})
74 }
75
76 fn memoize(&mut self, _: &Self) -> bool {
77 true
78 }
79}
80
81pub(crate) struct RootProps<P>(pub P);
83
84impl<P> Clone for RootProps<P>
85where
86 P: Clone,
87{
88 fn clone(&self) -> Self {
89 Self(self.0.clone())
90 }
91}
92
93impl<P> Properties for RootProps<P>
94where
95 P: Clone + 'static,
96{
97 type ComponentBuilder<RenderFn, Marker> = ComponentBuilder<RenderFn, P, Self, Marker>;
98
99 fn component_builder<RenderFn, Marker>(
100 _: RenderFn,
101 ) -> Self::ComponentBuilder<RenderFn, Marker> {
102 unreachable!("Root props technically are never built")
103 }
104
105 fn memoize(&mut self, _: &Self) -> bool {
106 true
107 }
108}
109
110pub struct EmptyBuilder;
113impl EmptyBuilder {
114 pub fn build(self) {}
115}
116
117#[rustversion::attr(
132 since(1.78.0),
133 diagnostic::on_unimplemented(
134 message = "`Component<{Props}>` is not implemented for `{Self}`",
135 label = "Component",
136 note = "Components are functions in the form `fn() -> Element`, `fn(props: Properties) -> Element`, or `#[component] fn(partial_eq1: u32, partial_eq2: u32) -> Element`.",
137 note = "You may have forgotten to add `#[component]` to your function to automatically implement the `ComponentFunction` trait."
138 )
139)]
140pub trait ComponentFunction<Props, Marker = ()>: Clone + 'static {
141 fn fn_ptr(&self) -> usize;
143
144 fn rebuild(&self, props: Props) -> Element;
146}
147
148pub trait ComponentFunctionExt<Props, Marker>: ComponentFunction<Props, Marker> + Sized
162where
163 Props: Properties,
164{
165 fn builder(self) -> Props::ComponentBuilder<Self, Marker> {
167 Props::component_builder(self)
168 }
169}
170
171impl<F, P, M> ComponentFunctionExt<P, M> for F
172where
173 F: ComponentFunction<P, M>,
174 P: Properties,
175{
176}
177
178#[must_use]
180pub struct ComponentBuilder<RenderFn, Builder, Props, Marker> {
181 render_fn: RenderFn,
182 builder: Builder,
183 _marker: PhantomData<fn() -> (Props, Marker)>,
184}
185
186impl<RenderFn, Builder, Props, Marker> ComponentBuilder<RenderFn, Builder, Props, Marker> {
187 pub(crate) fn new(render_fn: RenderFn, builder: Builder) -> Self {
189 Self {
190 render_fn,
191 builder,
192 _marker: PhantomData,
193 }
194 }
195
196 pub(crate) fn map_builder<NewBuilder>(
198 self,
199 map: impl FnOnce(Builder) -> NewBuilder,
200 ) -> ComponentBuilder<RenderFn, NewBuilder, Props, Marker> {
201 ComponentBuilder::new(self.render_fn, map(self.builder))
202 }
203}
204
205impl<RenderFn, Marker> ComponentBuilder<RenderFn, EmptyBuilder, (), Marker> {
206 pub fn build(self) -> ComponentBuilderOutput<RenderFn, (), Marker> {
208 self.builder.build();
209 ComponentBuilderOutput::new(self.render_fn, ())
210 }
211}
212
213impl<RenderFn, Builder, Props, Marker> HasAttributes
214 for ComponentBuilder<RenderFn, Builder, Props, Marker>
215where
216 Builder: HasAttributes,
217{
218 fn push_attribute<T>(
219 self,
220 name: &'static str,
221 ns: Option<&'static str>,
222 attr: impl IntoAttributeValue<T>,
223 volatile: bool,
224 ) -> Self {
225 self.map_builder(|builder| builder.push_attribute(name, ns, attr, volatile))
226 }
227}
228
229#[must_use]
231pub struct ComponentBuilderOutput<RenderFn, Props, Marker> {
232 render_fn: RenderFn,
233 props: Props,
234 _marker: PhantomData<fn() -> Marker>,
235}
236
237impl<RenderFn, Props, Marker> ComponentBuilderOutput<RenderFn, Props, Marker> {
238 pub fn new(render_fn: RenderFn, props: Props) -> Self {
240 Self {
241 render_fn,
242 props,
243 _marker: PhantomData,
244 }
245 }
246
247 pub fn into_vcomponent(self) -> VComponent
249 where
250 Props: ComponentBuilderRender<RenderFn, Marker>,
251 {
252 self.props.into_vcomponent(self.render_fn)
253 }
254
255 pub fn into_vnode(self) -> VNode
257 where
258 Props: ComponentBuilderRender<RenderFn, Marker>,
259 {
260 crate::view::ViewExt::into_vnode(self.into_vcomponent())
261 }
262}
263
264impl<RenderFn, Props, Marker> IntoDynNode for ComponentBuilderOutput<RenderFn, Props, Marker>
265where
266 Props: ComponentBuilderRender<RenderFn, Marker>,
267{
268 fn into_dyn_node(self) -> DynamicNode {
269 DynamicNode::Component(self.into_vcomponent())
270 }
271}
272
273impl<RenderFn, Props, Marker> IntoVNode for ComponentBuilderOutput<RenderFn, Props, Marker>
274where
275 Props: ComponentBuilderRender<RenderFn, Marker>,
276{
277 fn into_vnode(self) -> VNode {
278 ComponentBuilderOutput::into_vnode(self)
279 }
280}
281
282pub trait ComponentBuilderRender<RenderFn, Marker>: Sized {
284 fn into_vcomponent(self, render_fn: RenderFn) -> VComponent;
286}
287
288impl<RenderFn, Marker> ComponentBuilderRender<RenderFn, Marker> for ()
289where
290 RenderFn: ComponentFunction<(), Marker>,
291 Marker: 'static,
292{
293 fn into_vcomponent(self, render_fn: RenderFn) -> VComponent {
294 <Self as Properties>::into_vcomponent(self, render_fn)
295 }
296}
297
298impl<F, P> ComponentFunction<P> for F
300where
301 F: Fn(P) -> Element + Clone + 'static,
302{
303 fn rebuild(&self, props: P) -> Element {
304 subsecond::HotFn::current(self.clone()).call((props,))
305 }
306
307 fn fn_ptr(&self) -> usize {
308 subsecond::HotFn::current(self.clone()).ptr_address().0 as usize
309 }
310}
311
312#[doc(hidden)]
314pub struct EmptyMarker;
315impl<F> ComponentFunction<(), EmptyMarker> for F
316where
317 F: Fn() -> Element + Clone + 'static,
318{
319 fn rebuild(&self, props: ()) -> Element {
320 subsecond::HotFn::current(self.clone()).call(props)
321 }
322
323 fn fn_ptr(&self) -> usize {
324 subsecond::HotFn::current(self.clone()).ptr_address().0 as usize
325 }
326}
327
328pub trait SuperInto<O, M = ()> {
330 fn super_into(self) -> O;
332}
333
334impl<T, O, M> SuperInto<O, M> for T
335where
336 O: SuperFrom<T, M>,
337{
338 fn super_into(self) -> O {
339 O::super_from(self)
340 }
341}
342
343pub trait SuperFrom<T, M = ()> {
345 fn super_from(_: T) -> Self;
347}
348
349impl<T, O> SuperFrom<T, ()> for O
351where
352 O: From<T>,
353{
354 fn super_from(input: T) -> Self {
355 Self::from(input)
356 }
357}
358
359#[doc(hidden)]
361pub struct OptionStringFromMarker;
362
363impl<'a> SuperFrom<&'a str, OptionStringFromMarker> for Option<String> {
364 fn super_from(input: &'a str) -> Self {
365 Some(String::from(input))
366 }
367}
368
369#[doc(hidden)]
371pub struct OptionArgumentsFromMarker;
372
373impl<'a> SuperFrom<Arguments<'a>, OptionArgumentsFromMarker> for Option<String> {
374 fn super_from(input: Arguments<'a>) -> Self {
375 Some(input.to_string())
376 }
377}
378
379#[doc(hidden)]
381pub struct OptionCallbackMarker<T>(std::marker::PhantomData<T>);
382
383impl<
385 Function: FnMut(Args) -> Spawn + 'static,
386 Args: 'static,
387 Spawn: SpawnIfAsync<Marker, Ret> + 'static,
388 Ret: 'static,
389 Marker,
390> SuperFrom<Function, OptionCallbackMarker<Marker>> for Option<Callback<Args, Ret>>
391{
392 fn super_from(input: Function) -> Self {
393 Some(Callback::new(input))
394 }
395}
396
397#[test]
398#[allow(unused)]
399fn optional_callback_compiles() {
400 fn compiles() {
401 let callback: Callback<i32, i32> = (|num| num * num).super_into();
403 let callback: Callback<i32, ()> = (|num| async move { println!("{num}") }).super_into();
404
405 let optional: Option<Callback<i32, i32>> = (|num| num * num).super_into();
407 let optional: Option<Callback<i32, ()>> =
408 (|num| async move { println!("{num}") }).super_into();
409 }
410}
411
412#[test]
413#[allow(unused)]
414fn from_props_compiles() {
415 let option: i32 = 0i32.super_into();
417 let option: i32 = 0.super_into(); let option: i128 = 0.super_into();
419 let option: &'static str = "hello world".super_into();
420
421 let option: i64 = 0i32.super_into();
423 let option: String = "hello world".super_into();
424
425 let option: Option<i32> = 0i32.super_into();
427 let option: Option<i32> = 0.super_into();
428 let option: Option<i128> = 0.super_into();
429 fn takes_option_string<M>(_: impl SuperInto<Option<String>, M>) {}
430 takes_option_string("hello world");
431 takes_option_string("hello world".to_string());
432}