1use std::any::{Any, TypeId};
99use std::cell::{Cell, OnceCell, RefCell};
100use std::collections::HashMap;
101use std::hash::{Hash, Hasher};
102use std::ops::Deref;
103use std::rc::Rc;
104
105use frus_core::{Rect, Scene};
106use frus_layout::Style;
107
108use crate::callback::Callback;
109use crate::interaction::{Status, WidgetId};
110use crate::runtime::Runtime;
111use crate::theme::Theme;
112use crate::widget::Widget;
113
114thread_local! {
119 static REBUILD: Cell<bool> = const { Cell::new(false) };
120 static DEPTH: Cell<u32> = const { Cell::new(0) };
121 static INTERVALS: RefCell<Vec<Interval>> = const { RefCell::new(Vec::new()) };
122 static BACK_BLOCKS: Cell<u32> = const { Cell::new(0) };
123}
124
125pub fn back_blocked() -> bool {
129 BACK_BLOCKS.with(|blocks| blocks.get() > 0)
130}
131
132#[derive(Clone)]
134pub struct Interval {
135 pub id: u64,
138 pub period: std::time::Duration,
140 pub callback: Callback,
142}
143
144pub fn intervals() -> Vec<Interval> {
147 INTERVALS.with(|intervals| intervals.borrow().clone())
148}
149
150pub fn request_rebuild() {
154 REBUILD.with(|flag| flag.set(true));
155}
156
157pub fn rebuild_requested() -> bool {
159 REBUILD.with(|flag| flag.get())
160}
161
162pub fn take_rebuild_request() -> bool {
165 REBUILD.with(|flag| flag.replace(false))
166}
167
168struct Deeper(u32);
172
173impl Deeper {
174 fn enter() -> Self {
175 let now = DEPTH.with(|d| d.get());
176 DEPTH.with(|d| d.set(now + 1));
177 Deeper(now)
178 }
179}
180
181impl Drop for Deeper {
182 fn drop(&mut self) {
183 DEPTH.with(|d| d.set(self.0));
184 }
185}
186
187type StateKey = (WidgetId, u32);
194
195#[derive(Default)]
198pub struct StateStore {
199 entries: RefCell<HashMap<StateKey, Entry>>,
200 epoch: Cell<u64>,
202 building: Cell<bool>,
204 effects: RefCell<Vec<PendingEffect>>,
205 services: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
206}
207
208struct Entry {
209 kind: TypeId,
210 epoch: u64,
211 state: Option<Rc<dyn Any>>,
212 hooks: Rc<Hooks>,
213 dispose: Option<Box<dyn FnOnce()>>,
214}
215
216impl Entry {
217 fn dispose_all(self) {
218 if let Some(dispose) = self.dispose {
219 dispose();
220 }
221 self.hooks.dispose();
222 }
223}
224
225type Cleanup = Box<dyn FnOnce()>;
227
228struct PendingEffect {
229 hooks: Rc<Hooks>,
230 index: usize,
231 run: Box<dyn FnOnce() -> Option<Cleanup>>,
232}
233
234impl StateStore {
235 pub fn begin_build(&self) {
237 self.epoch.set(self.epoch.get() + 1);
238 self.building.set(true);
239 INTERVALS.with(|intervals| intervals.borrow_mut().clear());
242 BACK_BLOCKS.with(|blocks| blocks.set(0));
243 }
244
245 pub fn end_frame(&self) {
252 if self.building.replace(false) {
253 self.sweep();
254 }
255 self.flush_effects();
256 }
257
258 pub fn provide<T: 'static>(&self, value: Rc<T>) {
261 self.services.borrow_mut().insert(TypeId::of::<T>(), value);
262 }
263
264 pub fn service<T: 'static>(&self) -> Option<Rc<T>> {
266 self.services
267 .borrow()
268 .get(&TypeId::of::<T>())
269 .and_then(|s| s.clone().downcast::<T>().ok())
270 }
271
272 pub fn len(&self) -> usize {
274 self.entries.borrow().len()
275 }
276
277 pub fn is_empty(&self) -> bool {
279 self.entries.borrow().is_empty()
280 }
281
282 fn sweep(&self) {
283 let epoch = self.epoch.get();
284 let stale: Vec<Entry> = {
285 let mut entries = self.entries.borrow_mut();
286 let keys: Vec<StateKey> = entries
287 .iter()
288 .filter(|(_, entry)| entry.epoch < epoch)
289 .map(|(key, _)| *key)
290 .collect();
291 keys.into_iter()
292 .filter_map(|key| entries.remove(&key))
293 .collect()
294 };
295 for entry in stale {
296 entry.dispose_all();
297 }
298 }
299
300 fn flush_effects(&self) {
301 let pending: Vec<PendingEffect> = std::mem::take(&mut *self.effects.borrow_mut());
302 for effect in pending {
303 if effect.hooks.disposed.get() {
304 continue;
305 }
306 let previous = effect.hooks.take_cleanup(effect.index);
307 if let Some(cleanup) = previous {
308 cleanup();
309 }
310 let cleanup = (effect.run)();
311 effect.hooks.set_cleanup(effect.index, cleanup);
312 }
313 }
314
315 fn hooks_for(&self, key: StateKey, kind: TypeId) -> Rc<Hooks> {
318 let stale = {
319 let mut entries = self.entries.borrow_mut();
320 match entries.get_mut(&key) {
321 Some(entry) if entry.kind == kind => {
322 entry.epoch = self.epoch.get();
323 return entry.hooks.clone();
324 }
325 Some(_) => entries.remove(&key),
326 None => None,
327 }
328 };
329 if let Some(stale) = stale {
330 stale.dispose_all();
331 }
332 let hooks = Rc::new(Hooks::default());
333 self.entries.borrow_mut().insert(
334 key,
335 Entry {
336 kind,
337 epoch: self.epoch.get(),
338 state: None,
339 hooks: hooks.clone(),
340 dispose: None,
341 },
342 );
343 hooks
344 }
345
346 fn mount<W: StatefulWidget>(&self, key: StateKey, widget: Rc<W>) -> Mounted<W> {
349 let kind = TypeId::of::<W>();
350 let found = {
351 let mut entries = self.entries.borrow_mut();
352 entries
353 .get_mut(&key)
354 .filter(|entry| entry.kind == kind)
355 .map(|entry| {
356 entry.epoch = self.epoch.get();
357 (entry.state.clone(), entry.hooks.clone())
358 })
359 };
360 if let Some((Some(state), hooks)) = found {
361 let cell = state
362 .downcast::<StateCell<W::State>>()
363 .unwrap_or_else(|_| unreachable!("an entry's kind says what its state is"));
364 let previous = std::mem::replace(&mut *cell.widget.borrow_mut(), widget);
365 return Mounted {
366 cell,
367 hooks,
368 previous: Some(previous),
369 };
370 }
371 let stale = self.entries.borrow_mut().remove(&key);
372 if let Some(stale) = stale {
373 stale.dispose_all();
374 }
375 let cell = Rc::new(StateCell {
376 state: RefCell::new(widget.create_state()),
377 widget: RefCell::new(widget),
378 mounted: Cell::new(true),
379 });
380 let hooks = Rc::new(Hooks::default());
381 let leaving = cell.clone();
382 self.entries.borrow_mut().insert(
383 key,
384 Entry {
385 kind,
386 epoch: self.epoch.get(),
387 state: Some(cell.clone() as Rc<dyn Any>),
388 hooks: hooks.clone(),
389 dispose: Some(Box::new(move || {
390 leaving.mounted.set(false);
391 if let Ok(mut state) = leaving.state.try_borrow_mut() {
392 state.dispose();
393 }
394 })),
395 },
396 );
397 Mounted {
398 cell,
399 hooks,
400 previous: None,
401 }
402 }
403}
404
405struct Mounted<W: StatefulWidget> {
406 cell: Rc<StateCell<W::State>>,
407 hooks: Rc<Hooks>,
408 previous: Option<Rc<W>>,
409}
410
411const ORDER: &str = "hooks were called in a different order than on the last build: call every \
416 hook unconditionally, in the same order, never inside an `if`, a loop or a \
417 closure";
418
419#[derive(Default)]
420struct Hooks {
421 slots: RefCell<Vec<Slot>>,
422 disposed: Cell<bool>,
423}
424
425enum Slot {
426 Value(Rc<dyn Any>),
427 Effect {
428 deps: Box<dyn Any>,
429 cleanup: Option<Cleanup>,
430 },
431}
432
433impl Hooks {
434 fn value<T: 'static>(&self, index: usize, init: impl FnOnce() -> T) -> Rc<T> {
436 let existing = self.slots.borrow().get(index).map(|slot| match slot {
437 Slot::Value(value) => Some(value.clone()),
438 Slot::Effect { .. } => None,
439 });
440 match existing {
441 Some(Some(value)) => value.downcast::<T>().unwrap_or_else(|_| panic!("{ORDER}")),
442 Some(None) => panic!("{ORDER}"),
443 None => {
444 let value = Rc::new(init());
445 let mut slots = self.slots.borrow_mut();
446 assert_eq!(slots.len(), index, "{ORDER}");
447 slots.push(Slot::Value(value.clone() as Rc<dyn Any>));
448 value
449 }
450 }
451 }
452
453 fn take_cleanup(&self, index: usize) -> Option<Cleanup> {
454 match self.slots.borrow_mut().get_mut(index) {
455 Some(Slot::Effect { cleanup, .. }) => cleanup.take(),
456 _ => None,
457 }
458 }
459
460 fn set_cleanup(&self, index: usize, cleanup: Option<Cleanup>) {
461 if let Some(Slot::Effect { cleanup: slot, .. }) = self.slots.borrow_mut().get_mut(index) {
462 *slot = cleanup;
463 }
464 }
465
466 fn dispose(&self) {
467 self.disposed.set(true);
468 let cleanups: Vec<Cleanup> = self
469 .slots
470 .borrow_mut()
471 .iter_mut()
472 .filter_map(|slot| match slot {
473 Slot::Effect { cleanup, .. } => cleanup.take(),
474 Slot::Value(_) => None,
475 })
476 .collect();
477 for cleanup in cleanups {
478 cleanup();
479 }
480 }
481}
482
483pub trait IntoCleanup {
485 fn into_cleanup(self) -> Option<Box<dyn FnOnce()>>;
487}
488
489impl IntoCleanup for () {
490 fn into_cleanup(self) -> Option<Cleanup> {
491 None
492 }
493}
494
495impl<F: FnOnce() + 'static> IntoCleanup for F {
496 fn into_cleanup(self) -> Option<Cleanup> {
497 Some(Box::new(self))
498 }
499}
500
501pub struct UseState<T> {
507 cell: Rc<RefCell<T>>,
508}
509
510impl<T> Clone for UseState<T> {
511 fn clone(&self) -> Self {
512 Self {
513 cell: self.cell.clone(),
514 }
515 }
516}
517
518impl<T: 'static> UseState<T> {
519 pub fn get(&self) -> T
521 where
522 T: Clone,
523 {
524 self.cell.borrow().clone()
525 }
526
527 pub fn with<R>(&self, read: impl FnOnce(&T) -> R) -> R {
529 read(&self.cell.borrow())
530 }
531
532 pub fn set(&self, value: T) {
534 *self.cell.borrow_mut() = value;
535 request_rebuild();
536 }
537
538 pub fn update(&self, change: impl FnOnce(&mut T)) {
540 change(&mut self.cell.borrow_mut());
541 request_rebuild();
542 }
543}
544
545impl<T: std::fmt::Debug> std::fmt::Debug for UseState<T> {
546 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
547 f.debug_tuple("UseState")
548 .field(&*self.cell.borrow())
549 .finish()
550 }
551}
552
553pub struct UseRef<T> {
556 cell: Rc<RefCell<T>>,
557}
558
559impl<T> Clone for UseRef<T> {
560 fn clone(&self) -> Self {
561 Self {
562 cell: self.cell.clone(),
563 }
564 }
565}
566
567impl<T: 'static> UseRef<T> {
568 pub fn get(&self) -> T
570 where
571 T: Clone,
572 {
573 self.cell.borrow().clone()
574 }
575
576 pub fn with<R>(&self, read: impl FnOnce(&T) -> R) -> R {
578 read(&self.cell.borrow())
579 }
580
581 pub fn set(&self, value: T) {
583 *self.cell.borrow_mut() = value;
584 }
585
586 pub fn update(&self, change: impl FnOnce(&mut T)) {
588 change(&mut self.cell.borrow_mut());
589 }
590}
591
592pub struct BuildContext<'a> {
599 key: StateKey,
600 kind: TypeId,
601 runtime: &'a Runtime,
602 theme: &'a Theme,
603 hooks: OnceCell<Rc<Hooks>>,
604 cursor: Cell<usize>,
605 intervals: Cell<usize>,
606}
607
608impl<'a> BuildContext<'a> {
609 fn new(key: StateKey, kind: TypeId, runtime: &'a Runtime, theme: &'a Theme) -> Self {
610 Self {
611 key,
612 kind,
613 runtime,
614 theme,
615 hooks: OnceCell::new(),
616 cursor: Cell::new(0),
617 intervals: Cell::new(0),
618 }
619 }
620
621 pub fn theme(&self) -> &Theme {
623 self.theme
624 }
625
626 pub fn id(&self) -> WidgetId {
628 self.key.0
629 }
630
631 pub fn runtime(&self) -> &Runtime {
633 self.runtime
634 }
635
636 pub fn service<T: 'static>(&self) -> Option<Rc<T>> {
638 self.runtime.states.service::<T>()
639 }
640
641 fn hooks(&self) -> Rc<Hooks> {
642 self.hooks
643 .get_or_init(|| self.runtime.states.hooks_for(self.key, self.kind))
644 .clone()
645 }
646
647 fn next_slot(&self) -> usize {
648 let slot = self.cursor.get();
649 self.cursor.set(slot + 1);
650 slot
651 }
652
653 pub fn use_state<T: 'static>(&self, init: impl FnOnce() -> T) -> UseState<T> {
665 let cell = self
666 .hooks()
667 .value(self.next_slot(), || RefCell::new(init()));
668 UseState { cell }
669 }
670
671 pub fn use_ref<T: 'static>(&self, init: impl FnOnce() -> T) -> UseRef<T> {
674 let cell = self
675 .hooks()
676 .value(self.next_slot(), || RefCell::new(init()));
677 UseRef { cell }
678 }
679
680 pub fn use_memo<D: PartialEq + 'static, T: 'static>(
683 &self,
684 deps: D,
685 compute: impl FnOnce() -> T,
686 ) -> Rc<T> {
687 let memo = self
688 .hooks()
689 .value(self.next_slot(), || RefCell::new(None::<(D, Rc<T>)>));
690 let mut memo = memo.borrow_mut();
691 match memo.as_ref() {
692 Some((previous, value)) if *previous == deps => value.clone(),
693 _ => {
694 let value = Rc::new(compute());
695 *memo = Some((deps, value.clone()));
696 value
697 }
698 }
699 }
700
701 pub fn use_interval(&self, period: std::time::Duration, on_tick: impl Into<Callback>) {
722 let index = self.intervals.get();
723 self.intervals.set(index + 1);
724 let id = {
725 let mut hasher = std::collections::hash_map::DefaultHasher::new();
726 (self.key.0.as_u64(), self.key.1, index, period.as_millis()).hash(&mut hasher);
727 hasher.finish()
728 };
729 INTERVALS.with(|intervals| {
730 intervals.borrow_mut().push(Interval {
731 id,
732 period,
733 callback: on_tick.into(),
734 })
735 });
736 }
737
738 pub fn block_back(&self, blocked: bool) {
745 if blocked {
746 BACK_BLOCKS.with(|blocks| blocks.set(blocks.get() + 1));
747 }
748 }
749
750 pub fn use_effect<D, C>(&self, deps: D, effect: impl FnOnce() -> C + 'static)
767 where
768 D: PartialEq + 'static,
769 C: IntoCleanup,
770 {
771 let hooks = self.hooks();
772 let index = self.next_slot();
773 let changed = {
774 let mut slots = hooks.slots.borrow_mut();
775 match slots.get_mut(index) {
776 None => {
777 assert_eq!(slots.len(), index, "{ORDER}");
778 slots.push(Slot::Effect {
779 deps: Box::new(deps),
780 cleanup: None,
781 });
782 true
783 }
784 Some(Slot::Effect { deps: old, .. }) => {
785 if old.downcast_ref::<D>().is_some_and(|old| *old == deps) {
786 false
787 } else {
788 *old = Box::new(deps);
789 true
790 }
791 }
792 Some(Slot::Value(_)) => panic!("{ORDER}"),
793 }
794 };
795 if changed {
796 self.runtime
797 .states
798 .effects
799 .borrow_mut()
800 .push(PendingEffect {
801 hooks,
802 index,
803 run: Box::new(move || effect().into_cleanup()),
804 });
805 }
806 }
807}
808
809pub trait StatelessWidget: 'static {
819 fn build(&self, cx: &BuildContext) -> Box<dyn Widget>;
821
822 fn into_widget(self) -> Component
825 where
826 Self: Sized,
827 {
828 Component::stateless(self)
829 }
830}
831
832impl<F> StatelessWidget for F
833where
834 F: Fn(&BuildContext) -> Box<dyn Widget> + 'static,
835{
836 fn build(&self, cx: &BuildContext) -> Box<dyn Widget> {
837 self(cx)
838 }
839}
840
841pub trait StatefulWidget: 'static {
848 type State: State<Widget = Self>;
850
851 fn create_state(&self) -> Self::State;
853
854 fn into_widget(self) -> Component
856 where
857 Self: Sized,
858 {
859 Component::stateful(self)
860 }
861}
862
863pub trait State: Sized + 'static {
865 type Widget: StatefulWidget<State = Self>;
867
868 fn init_state(&mut self, _cx: &StateContext<Self>) {}
872
873 fn did_update_widget(&mut self, _old: &Self::Widget, _cx: &StateContext<Self>) {}
876
877 fn dispose(&mut self) {}
879
880 fn build(&self, cx: &StateContext<Self>) -> Box<dyn Widget>;
887}
888
889pub(crate) struct StateCell<S: State> {
890 state: RefCell<S>,
891 widget: RefCell<Rc<S::Widget>>,
892 mounted: Cell<bool>,
893}
894
895pub struct StateHandle<S: State> {
898 cell: Rc<StateCell<S>>,
899}
900
901impl<S: State> Clone for StateHandle<S> {
902 fn clone(&self) -> Self {
903 Self {
904 cell: self.cell.clone(),
905 }
906 }
907}
908
909impl<S: State> StateHandle<S> {
910 pub fn set_state(&self, change: impl FnOnce(&mut S)) {
915 if !self.cell.mounted.get() {
916 return;
917 }
918 match self.cell.state.try_borrow_mut() {
919 Ok(mut state) => change(&mut state),
920 Err(_) => panic!(
921 "set_state was called while the state was being read — from inside `build`, or \
922 from inside another `set_state`. Change state from a handler, not while building"
923 ),
924 }
925 request_rebuild();
926 }
927
928 pub fn read<R>(&self, read: impl FnOnce(&S) -> R) -> R {
930 read(&self.cell.state.borrow())
931 }
932
933 pub fn widget(&self) -> Rc<S::Widget> {
935 self.cell.widget.borrow().clone()
936 }
937
938 pub fn is_mounted(&self) -> bool {
940 self.cell.mounted.get()
941 }
942
943 pub fn callback(&self, change: impl Fn(&mut S) + 'static) -> Callback {
945 let handle = self.clone();
946 Callback::new(move || handle.set_state(|state| change(state)))
947 }
948
949 pub fn handler<T: Clone + 'static>(
956 &self,
957 change: impl Fn(&mut S, T) + 'static,
958 ) -> impl Fn(T) -> Callback + 'static {
959 let handle = self.clone();
960 let change = Rc::new(change);
961 move |value| {
962 let (handle, change) = (handle.clone(), change.clone());
963 Callback::new(move || handle.set_state(|state| change(state, value.clone())))
964 }
965 }
966}
967
968pub struct StateContext<'c, 'a, S: State> {
971 cx: &'c BuildContext<'a>,
972 cell: &'c Rc<StateCell<S>>,
973}
974
975impl<'a, S: State> Deref for StateContext<'_, 'a, S> {
976 type Target = BuildContext<'a>;
977
978 fn deref(&self) -> &BuildContext<'a> {
979 self.cx
980 }
981}
982
983impl<S: State> StateContext<'_, '_, S> {
984 pub fn widget(&self) -> Rc<S::Widget> {
986 self.cell.widget.borrow().clone()
987 }
988
989 pub fn handle(&self) -> StateHandle<S> {
991 StateHandle {
992 cell: self.cell.clone(),
993 }
994 }
995
996 pub fn callback(&self, change: impl Fn(&mut S) + 'static) -> Callback {
998 self.handle().callback(change)
999 }
1000
1001 pub fn handler<T: Clone + 'static>(
1004 &self,
1005 change: impl Fn(&mut S, T) + 'static,
1006 ) -> impl Fn(T) -> Callback + 'static {
1007 self.handle().handler(change)
1008 }
1009}
1010
1011type Builder<Msg> = Box<dyn FnOnce(&BuildContext) -> Box<dyn Widget<Msg>>>;
1016
1017pub struct Component<Msg = Callback> {
1024 inner: LazyChild<Msg>,
1025 key: Option<u64>,
1026}
1027
1028impl Component<Callback> {
1029 pub fn stateless<W: StatelessWidget>(widget: W) -> Self {
1031 Self::from_builder(
1032 TypeId::of::<W>(),
1033 Box::new(move |cx: &BuildContext| widget.build(cx)),
1034 )
1035 }
1036
1037 pub fn stateful<W: StatefulWidget>(widget: W) -> Self {
1039 Self::from_builder(
1040 TypeId::of::<W>(),
1041 Box::new(move |cx: &BuildContext| {
1042 let Mounted {
1043 cell,
1044 hooks,
1045 previous,
1046 } = cx.runtime.states.mount(cx.key, Rc::new(widget));
1047 let _ = cx.hooks.set(hooks);
1048 let state_cx = StateContext { cx, cell: &cell };
1049 match previous {
1050 None => cell.state.borrow_mut().init_state(&state_cx),
1051 Some(old) => cell.state.borrow_mut().did_update_widget(&old, &state_cx),
1052 }
1053 let built = cell.state.borrow().build(&state_cx);
1054 built
1055 }),
1056 )
1057 }
1058}
1059
1060impl<Msg> Component<Msg> {
1061 fn from_builder(kind: TypeId, build: Builder<Msg>) -> Self {
1062 Self {
1063 inner: LazyChild {
1064 kind,
1065 build: RefCell::new(Some(build)),
1066 built: OnceCell::new(),
1067 unbuilt: Unbuilt,
1068 },
1069 key: None,
1070 }
1071 }
1072
1073 pub fn with_key(mut self, key: impl Hash) -> Self {
1076 let mut hasher = std::collections::hash_map::DefaultHasher::new();
1077 key.hash(&mut hasher);
1078 self.key = Some(hasher.finish());
1079 self
1080 }
1081
1082 fn restyle(&self, base: Style) -> Style {
1084 base
1085 }
1086}
1087
1088struct LazyChild<Msg> {
1090 kind: TypeId,
1091 build: RefCell<Option<Builder<Msg>>>,
1092 built: OnceCell<Box<dyn Widget<Msg>>>,
1093 unbuilt: Unbuilt,
1094}
1095
1096impl<Msg> LazyChild<Msg> {
1097 fn expand(&self, id: WidgetId, runtime: &Runtime, theme: &Theme) {
1100 if self.built.get().is_none() {
1101 let build = self.build.borrow_mut().take();
1102 if let Some(build) = build {
1103 let depth = DEPTH.with(|d| d.get());
1104 let cx = BuildContext::new((id, depth), self.kind, runtime, theme);
1105 let child = build(&cx);
1106 let _ = self.built.set(child);
1107 }
1108 }
1109 if let Some(child) = self.built.get() {
1110 let _deeper = Deeper::enter();
1111 child.expand(id, runtime, theme);
1112 }
1113 }
1114
1115 fn build_in(&self, id: WidgetId, runtime: &Runtime, theme: &Theme) {
1116 if let Some(child) = self.built.get() {
1117 child.build_in(id, runtime, theme);
1118 }
1119 }
1120
1121 fn build_themed(&self, theme: &Theme) {
1122 if let Some(child) = self.built.get() {
1123 child.build_themed(theme);
1124 }
1125 }
1126
1127 fn children(&self) -> &[Box<dyn Widget<Msg>>] {
1128 match self.built.get() {
1129 Some(child) => child.children(),
1130 None => {
1131 #[cfg(debug_assertions)]
1132 if self.build.borrow().is_some() {
1133 panic!(
1134 "a component's children were read before it was built: a traversal \
1135 reached this tree before the walk that builds components did. Call \
1136 `build_deferred(tree, &theme, &runtime)` on it first"
1137 );
1138 }
1139 &[]
1140 }
1141 }
1142 }
1143}
1144
1145impl<Msg> Deref for LazyChild<Msg> {
1146 type Target = dyn Widget<Msg>;
1147
1148 fn deref(&self) -> &(dyn Widget<Msg> + 'static) {
1149 match self.built.get() {
1150 Some(child) => child.as_ref(),
1151 None => &self.unbuilt,
1152 }
1153 }
1154}
1155
1156struct Unbuilt;
1158
1159impl<Msg> Widget<Msg> for Unbuilt {
1160 fn style(&self) -> Style {
1161 Style::default()
1162 }
1163
1164 fn children(&self) -> &[Box<dyn Widget<Msg>>] {
1165 &[]
1166 }
1167
1168 fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {}
1169
1170 fn on_click(&self) -> Option<Msg> {
1171 None
1172 }
1173
1174 fn debug_name(&self) -> &'static str {
1175 "Component"
1176 }
1177}
1178
1179crate::transparent::forward_transparent!(Component {
1180 fn key(&self) -> Option<u64> {
1184 self.key
1185 }
1186
1187 fn positioned(&self) -> Option<crate::positioned::Positioning> {
1188 self.inner.positioned()
1189 }
1190
1191 fn theme_override(
1192 &self,
1193 inherited: &crate::theme::Theme,
1194 ) -> Option<Box<crate::theme::Theme>> {
1195 self.inner.theme_override(inherited)
1196 }
1197
1198 fn media_override(&self, inherited: crate::MediaQuery) -> Option<crate::MediaQuery> {
1199 self.inner.media_override(inherited)
1200 }
1201
1202 fn scaffold_override(&self) -> Option<crate::ScaffoldInfo> {
1203 self.inner.scaffold_override()
1204 }
1205
1206 fn autofill_group(&self) -> bool {
1207 self.inner.autofill_group()
1208 }
1209});
1210
1211pub fn component(build: impl Fn(&BuildContext) -> Box<dyn Widget> + 'static) -> Component {
1224 Component::stateless(build)
1225}
1226
1227#[cfg(test)]
1228mod tests {
1229 use super::*;
1230 use crate::{build_deferred, keyed, Container, Flex};
1231 use frus_layout::Dimension;
1232
1233 type Builder = Rc<dyn Fn(&BuildContext) -> Box<dyn Widget>>;
1234 type Log = Rc<RefCell<Vec<String>>>;
1235 type Handle = Rc<RefCell<Option<StateHandle<LifeState>>>>;
1236
1237 fn log() -> Log {
1238 Rc::new(RefCell::new(Vec::new()))
1239 }
1240
1241 fn taken(log: &Log) -> Vec<String> {
1242 std::mem::take(&mut *log.borrow_mut())
1243 }
1244
1245 fn frame(runtime: &Runtime, root: &dyn Widget) {
1248 runtime.states.begin_build();
1249 build_deferred(root, &Theme::default(), runtime);
1250 runtime.states.end_frame();
1251 }
1252
1253 struct Life {
1255 tag: u32,
1256 log: Log,
1257 out: Handle,
1258 }
1259
1260 struct LifeState {
1261 log: Log,
1262 out: Handle,
1263 count: i32,
1264 }
1265
1266 impl StatefulWidget for Life {
1267 type State = LifeState;
1268 fn create_state(&self) -> LifeState {
1269 LifeState {
1270 log: self.log.clone(),
1271 out: self.out.clone(),
1272 count: 0,
1273 }
1274 }
1275 }
1276
1277 impl State for LifeState {
1278 type Widget = Life;
1279 fn init_state(&mut self, cx: &StateContext<Self>) {
1280 self.log
1281 .borrow_mut()
1282 .push(format!("init {}", cx.widget().tag));
1283 *self.out.borrow_mut() = Some(cx.handle());
1284 }
1285 fn did_update_widget(&mut self, old: &Life, cx: &StateContext<Self>) {
1286 self.log
1287 .borrow_mut()
1288 .push(format!("update {}->{}", old.tag, cx.widget().tag));
1289 }
1290 fn dispose(&mut self) {
1291 self.log.borrow_mut().push("dispose".to_string());
1292 }
1293 fn build(&self, _cx: &StateContext<Self>) -> Box<dyn Widget> {
1294 Box::new(Container::new().width(10.0 + self.count as f32).height(4.0))
1295 }
1296 }
1297
1298 fn life(tag: u32, log: &Log, out: &Handle) -> Component {
1299 Life {
1300 tag,
1301 log: log.clone(),
1302 out: out.clone(),
1303 }
1304 .into_widget()
1305 }
1306
1307 fn handle() -> Handle {
1308 Rc::new(RefCell::new(None))
1309 }
1310
1311 fn width(widget: &dyn Widget) -> Dimension {
1312 widget.style().width
1313 }
1314
1315 #[test]
1316 fn a_stateless_component_is_the_widget_it_builds() {
1317 let runtime = Runtime::default();
1318 let root = component(|_| Box::new(Container::new().width(120.0).height(30.0)));
1319 frame(&runtime, &root);
1320 assert_eq!(width(&root), Dimension::Length(120.0));
1321 assert_eq!(root.style().height, Dimension::Length(30.0));
1322 }
1323
1324 #[test]
1325 fn a_state_is_made_once_and_survives_rebuilds() {
1326 let (runtime, log, out) = (Runtime::default(), log(), handle());
1327 let first = life(1, &log, &out);
1328 frame(&runtime, &first);
1329 assert_eq!(taken(&log), ["init 1"]);
1330 assert_eq!(width(&first), Dimension::Length(10.0));
1331
1332 out.borrow().as_ref().unwrap().set_state(|s| s.count = 5);
1334 assert!(take_rebuild_request(), "set_state asks for a rebuild");
1335
1336 let second = life(2, &log, &out);
1337 frame(&runtime, &second);
1338 assert_eq!(
1339 taken(&log),
1340 ["update 1->2"],
1341 "the same place, the same kind: the state is kept and told what changed"
1342 );
1343 assert_eq!(
1344 width(&second),
1345 Dimension::Length(15.0),
1346 "and it is the kept one"
1347 );
1348 }
1349
1350 #[test]
1351 fn a_handler_carries_the_value_a_widget_reports_into_the_state() {
1352 let (runtime, log, out) = (Runtime::default(), log(), handle());
1353 frame(&runtime, &life(1, &log, &out));
1354 let handle = out.borrow().clone().unwrap();
1355 let on_value = handle.handler(|s, by: i32| s.count += by);
1356 let (three, four) = (on_value(3), on_value(4));
1357 assert_eq!(
1358 handle.read(|s| s.count),
1359 0,
1360 "nothing changes until it is delivered"
1361 );
1362 three.call();
1363 four.call();
1364 four.call();
1365 assert_eq!(
1366 handle.read(|s| s.count),
1367 11,
1368 "each delivery makes the change"
1369 );
1370 assert!(take_rebuild_request(), "and it asks for the rebuild");
1371 }
1372
1373 #[test]
1374 fn a_component_that_blocks_back_does_so_for_one_build_only() {
1375 let runtime = Runtime::default();
1376 let open = Rc::new(Cell::new(true));
1377 let asked = open.clone();
1378 let root = component(move |cx| {
1379 cx.block_back(asked.get());
1380 Box::new(Container::new().width(4.0).height(4.0))
1381 });
1382 frame(&runtime, &root);
1383 assert!(back_blocked(), "an open menu is in the way");
1384 open.set(false);
1385 frame(&runtime, &root);
1386 assert!(!back_blocked(), "each build says it afresh");
1387 }
1388
1389 #[test]
1390 fn a_state_is_disposed_when_a_rebuild_no_longer_reaches_it() {
1391 let (runtime, log, out) = (Runtime::default(), log(), handle());
1392 frame(&runtime, &Flex::column().child(life(1, &log, &out)));
1393 taken(&log);
1394 assert_eq!(runtime.states.len(), 1);
1395
1396 frame(&runtime, &Flex::column().child(Container::new()));
1397 assert_eq!(taken(&log), ["dispose"]);
1398 assert!(runtime.states.is_empty());
1399 let handle = out.borrow().clone().unwrap();
1400 assert!(!handle.is_mounted());
1401 take_rebuild_request();
1402 handle.set_state(|s| s.count = 9);
1403 assert!(
1404 !take_rebuild_request(),
1405 "a state that has left cannot ask for a rebuild"
1406 );
1407 }
1408
1409 #[test]
1410 fn a_different_kind_of_widget_in_the_same_place_starts_over() {
1411 let (runtime, log, out) = (Runtime::default(), log(), handle());
1412 frame(&runtime, &Flex::column().child(life(1, &log, &out)));
1413 taken(&log);
1414 let other = component(|cx| {
1416 cx.use_state(|| 0u8);
1417 Box::new(Container::new())
1418 });
1419 frame(&runtime, &Flex::column().child(other));
1420 assert_eq!(
1421 taken(&log),
1422 ["dispose"],
1423 "the old kind is disposed, not reused"
1424 );
1425 }
1426
1427 #[test]
1428 fn a_key_carries_the_state_wherever_the_siblings_go() {
1429 let (runtime, log) = (Runtime::default(), log());
1430 let (a, b) = (handle(), handle());
1431 let column = |order: [u32; 2]| {
1432 let mut column = Flex::column();
1433 for tag in order {
1434 let out = if tag == 1 { &a } else { &b };
1435 column = column.child(keyed(tag, life(tag, &log, out)));
1436 }
1437 column
1438 };
1439 frame(&runtime, &column([1, 2]));
1440 a.borrow().as_ref().unwrap().set_state(|s| s.count = 1);
1441 b.borrow().as_ref().unwrap().set_state(|s| s.count = 2);
1442 taken(&log);
1443
1444 frame(&runtime, &column([2, 1]));
1445 let counts = |h: &Handle| h.borrow().as_ref().unwrap().read(|s| s.count);
1446 assert_eq!(
1447 (counts(&a), counts(&b)),
1448 (1, 2),
1449 "each state stayed with its key"
1450 );
1451 assert_eq!(runtime.states.len(), 2);
1452 assert!(
1453 !taken(&log)
1454 .iter()
1455 .any(|line| line == "dispose" || line.starts_with("init")),
1456 "and neither was made again or let go"
1457 );
1458 }
1459
1460 #[test]
1461 fn a_component_that_builds_a_component_keeps_both_states() {
1462 let (runtime, log, out) = (Runtime::default(), log(), handle());
1463 let (inner_log, inner_out) = (log.clone(), out.clone());
1464 let outer = component(move |cx| {
1465 cx.use_state(|| 0);
1466 Box::new(life(7, &inner_log, &inner_out))
1468 });
1469 frame(&runtime, &outer);
1470 assert_eq!(
1471 runtime.states.len(),
1472 2,
1473 "one for the hooks, one for the state"
1474 );
1475 assert_eq!(taken(&log), ["init 7"]);
1476 }
1477
1478 #[test]
1479 fn hooks_keep_their_values_between_builds() {
1480 let runtime = Runtime::default();
1481 let seen = Rc::new(RefCell::new(Vec::new()));
1482 let build = {
1484 let seen = seen.clone();
1485 Rc::new(move |cx: &BuildContext| -> Box<dyn Widget> {
1486 let taps = cx.use_state(|| 10);
1487 let scratch = cx.use_ref(|| 0);
1488 scratch.update(|n| *n += 1);
1489 taps.update(|n| *n += 1);
1490 seen.borrow_mut().push((taps.get(), scratch.get()));
1491 Box::new(Container::new())
1492 })
1493 };
1494 let make = |build: &Builder| {
1495 let build = build.clone();
1496 Component::stateless(move |cx: &BuildContext| build(cx))
1497 };
1498 let build: Builder = build;
1499 frame(&runtime, &make(&build));
1500 assert!(
1501 take_rebuild_request(),
1502 "changing a state hook asks for a rebuild"
1503 );
1504 frame(&runtime, &make(&build));
1505 frame(&runtime, &make(&build));
1506 assert_eq!(*seen.borrow(), [(11, 1), (12, 2), (13, 3)]);
1507 }
1508
1509 #[test]
1510 fn use_memo_recomputes_only_when_its_dependencies_change() {
1511 let runtime = Runtime::default();
1512 let computed = Rc::new(Cell::new(0));
1513 let dep = Rc::new(Cell::new(1));
1514 let build: Builder = {
1515 let (computed, dep) = (computed.clone(), dep.clone());
1516 Rc::new(move |cx| {
1517 let d = dep.get();
1518 let value = cx.use_memo(d, || {
1519 computed.set(computed.get() + 1);
1520 d * 100
1521 });
1522 Box::new(Container::new().width(*value as f32))
1523 })
1524 };
1525 let make = || {
1526 let build = build.clone();
1527 Component::stateless(move |cx: &BuildContext| build(cx))
1528 };
1529 let root = make();
1530 frame(&runtime, &root);
1531 assert_eq!(
1532 (computed.get(), width(&root)),
1533 (1, Dimension::Length(100.0))
1534 );
1535 let root = make();
1536 frame(&runtime, &root);
1537 assert_eq!(computed.get(), 1, "the same dependency: not computed again");
1538 dep.set(2);
1539 let root = make();
1540 frame(&runtime, &root);
1541 assert_eq!(
1542 (computed.get(), width(&root)),
1543 (2, Dimension::Length(200.0))
1544 );
1545 }
1546
1547 #[test]
1548 fn effects_run_after_the_build_and_clean_up_before_the_next_and_at_the_end() {
1549 let (runtime, log) = (Runtime::default(), log());
1550 let dep = Rc::new(Cell::new(1));
1551 let build: Builder = {
1552 let (log, dep) = (log.clone(), dep.clone());
1553 Rc::new(move |cx| {
1554 let d = dep.get();
1555 let log = log.clone();
1556 log.borrow_mut().push(format!("build {d}"));
1557 cx.use_effect(d, move || {
1558 log.borrow_mut().push(format!("effect {d}"));
1559 move || log.borrow_mut().push(format!("cleanup {d}"))
1560 });
1561 Box::new(Container::new())
1562 })
1563 };
1564 let make = || {
1565 let build = build.clone();
1566 Component::stateless(move |cx: &BuildContext| build(cx))
1567 };
1568 frame(&runtime, &make());
1569 assert_eq!(
1570 taken(&log),
1571 ["build 1", "effect 1"],
1572 "after the build, not during it"
1573 );
1574
1575 frame(&runtime, &make());
1576 assert_eq!(
1577 taken(&log),
1578 ["build 1"],
1579 "the same dependency: nothing runs"
1580 );
1581
1582 dep.set(2);
1583 frame(&runtime, &make());
1584 assert_eq!(
1585 taken(&log),
1586 ["build 2", "cleanup 1", "effect 2"],
1587 "the old one is undone before the new one starts"
1588 );
1589
1590 frame(&runtime, &Flex::column().child(Container::new()));
1592 assert_eq!(taken(&log), ["cleanup 2"]);
1593 }
1594
1595 #[test]
1596 #[should_panic(expected = "different order")]
1597 fn hooks_called_in_a_different_order_are_caught() {
1598 let runtime = Runtime::default();
1599 let first = Rc::new(Cell::new(true));
1600 let build: Builder = {
1601 let first = first.clone();
1602 Rc::new(move |cx| {
1603 if first.get() {
1604 cx.use_state(|| 1i32);
1605 }
1606 cx.use_ref(String::new);
1607 Box::new(Container::new())
1608 })
1609 };
1610 let make = || {
1611 let build = build.clone();
1612 Component::stateless(move |cx: &BuildContext| build(cx))
1613 };
1614 frame(&runtime, &make());
1615 first.set(false);
1616 frame(&runtime, &make());
1617 }
1618
1619 #[test]
1620 #[should_panic(expected = "set_state was called while")]
1621 fn changing_state_from_inside_build_is_refused() {
1622 struct Greedy;
1623 struct GreedyState;
1624 impl StatefulWidget for Greedy {
1625 type State = GreedyState;
1626 fn create_state(&self) -> GreedyState {
1627 GreedyState
1628 }
1629 }
1630 impl State for GreedyState {
1631 type Widget = Greedy;
1632 fn build(&self, cx: &StateContext<Self>) -> Box<dyn Widget> {
1633 cx.handle().set_state(|_| {});
1634 Box::new(Container::new())
1635 }
1636 }
1637 frame(&Runtime::default(), &Greedy.into_widget());
1638 }
1639
1640 #[test]
1641 fn an_interval_is_asked_for_by_each_build_that_wants_it() {
1642 let runtime = Runtime::default();
1643 let on = Rc::new(Cell::new(true));
1644 let build: Builder = {
1645 let on = on.clone();
1646 Rc::new(move |cx| {
1647 if on.get() {
1648 cx.use_interval(std::time::Duration::from_secs(1), || {});
1649 cx.use_interval(std::time::Duration::from_secs(5), || {});
1650 }
1651 Box::new(Container::new())
1652 })
1653 };
1654 let make = || {
1655 let build = build.clone();
1656 Component::stateless(move |cx: &BuildContext| build(cx))
1657 };
1658 frame(&runtime, &make());
1659 let first = intervals();
1660 assert_eq!(first.len(), 2);
1661 assert_ne!(first[0].id, first[1].id, "two timers are told apart");
1662
1663 frame(&runtime, &make());
1664 let second = intervals();
1665 assert_eq!(
1666 first.iter().map(|i| i.id).collect::<Vec<_>>(),
1667 second.iter().map(|i| i.id).collect::<Vec<_>>(),
1668 "and each is the same timer from one build to the next"
1669 );
1670
1671 on.set(false);
1672 frame(&runtime, &make());
1673 assert!(
1674 intervals().is_empty(),
1675 "a build that stops asking stops the timer"
1676 );
1677 }
1678
1679 #[test]
1680 fn a_service_provided_to_the_runtime_is_reachable_from_a_build() {
1681 let runtime = Runtime::default();
1682 runtime.states.provide(Rc::new(41u32));
1683 let root = component(|cx| {
1684 let n = cx.service::<u32>().expect("provided");
1685 Box::new(Container::new().width(*n as f32 + 1.0))
1686 });
1687 frame(&runtime, &root);
1688 assert_eq!(width(&root), Dimension::Length(42.0));
1689 assert!(runtime.states.service::<String>().is_none());
1690 }
1691}