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 #[cfg(test)]
623 pub(crate) fn for_test(runtime: &'a Runtime, theme: &'a Theme) -> Self {
624 Self::new((WidgetId::ROOT, 0), TypeId::of::<()>(), runtime, theme)
625 }
626
627 pub fn theme(&self) -> &Theme {
629 self.theme
630 }
631
632 pub fn id(&self) -> WidgetId {
634 self.key.0
635 }
636
637 pub fn runtime(&self) -> &Runtime {
639 self.runtime
640 }
641
642 pub fn service<T: 'static>(&self) -> Option<Rc<T>> {
644 self.runtime.states.service::<T>()
645 }
646
647 fn hooks(&self) -> Rc<Hooks> {
648 self.hooks
649 .get_or_init(|| self.runtime.states.hooks_for(self.key, self.kind))
650 .clone()
651 }
652
653 fn next_slot(&self) -> usize {
654 let slot = self.cursor.get();
655 self.cursor.set(slot + 1);
656 slot
657 }
658
659 pub fn use_state<T: 'static>(&self, init: impl FnOnce() -> T) -> UseState<T> {
671 let cell = self
672 .hooks()
673 .value(self.next_slot(), || RefCell::new(init()));
674 UseState { cell }
675 }
676
677 pub fn use_ref<T: 'static>(&self, init: impl FnOnce() -> T) -> UseRef<T> {
680 let cell = self
681 .hooks()
682 .value(self.next_slot(), || RefCell::new(init()));
683 UseRef { cell }
684 }
685
686 pub fn use_memo<D: PartialEq + 'static, T: 'static>(
689 &self,
690 deps: D,
691 compute: impl FnOnce() -> T,
692 ) -> Rc<T> {
693 let memo = self
694 .hooks()
695 .value(self.next_slot(), || RefCell::new(None::<(D, Rc<T>)>));
696 let mut memo = memo.borrow_mut();
697 match memo.as_ref() {
698 Some((previous, value)) if *previous == deps => value.clone(),
699 _ => {
700 let value = Rc::new(compute());
701 *memo = Some((deps, value.clone()));
702 value
703 }
704 }
705 }
706
707 pub fn use_interval(&self, period: std::time::Duration, on_tick: impl Into<Callback>) {
728 let index = self.intervals.get();
729 self.intervals.set(index + 1);
730 let id = {
731 let mut hasher = std::collections::hash_map::DefaultHasher::new();
732 (self.key.0.as_u64(), self.key.1, index, period.as_millis()).hash(&mut hasher);
733 hasher.finish()
734 };
735 INTERVALS.with(|intervals| {
736 intervals.borrow_mut().push(Interval {
737 id,
738 period,
739 callback: on_tick.into(),
740 })
741 });
742 }
743
744 pub fn block_back(&self, blocked: bool) {
751 if blocked {
752 BACK_BLOCKS.with(|blocks| blocks.set(blocks.get() + 1));
753 }
754 }
755
756 pub fn use_effect<D, C>(&self, deps: D, effect: impl FnOnce() -> C + 'static)
773 where
774 D: PartialEq + 'static,
775 C: IntoCleanup,
776 {
777 let hooks = self.hooks();
778 let index = self.next_slot();
779 let changed = {
780 let mut slots = hooks.slots.borrow_mut();
781 match slots.get_mut(index) {
782 None => {
783 assert_eq!(slots.len(), index, "{ORDER}");
784 slots.push(Slot::Effect {
785 deps: Box::new(deps),
786 cleanup: None,
787 });
788 true
789 }
790 Some(Slot::Effect { deps: old, .. }) => {
791 if old.downcast_ref::<D>().is_some_and(|old| *old == deps) {
792 false
793 } else {
794 *old = Box::new(deps);
795 true
796 }
797 }
798 Some(Slot::Value(_)) => panic!("{ORDER}"),
799 }
800 };
801 if changed {
802 self.runtime
803 .states
804 .effects
805 .borrow_mut()
806 .push(PendingEffect {
807 hooks,
808 index,
809 run: Box::new(move || effect().into_cleanup()),
810 });
811 }
812 }
813}
814
815pub trait StatelessWidget: 'static {
825 fn build(&self, cx: &BuildContext) -> Box<dyn Widget>;
827
828 fn into_widget(self) -> Component
831 where
832 Self: Sized,
833 {
834 Component::stateless(self)
835 }
836}
837
838impl<F> StatelessWidget for F
839where
840 F: Fn(&BuildContext) -> Box<dyn Widget> + 'static,
841{
842 fn build(&self, cx: &BuildContext) -> Box<dyn Widget> {
843 self(cx)
844 }
845}
846
847pub trait StatefulWidget: 'static {
854 type State: State<Widget = Self>;
856
857 fn create_state(&self) -> Self::State;
859
860 fn into_widget(self) -> Component
862 where
863 Self: Sized,
864 {
865 Component::stateful(self)
866 }
867}
868
869pub trait State: Sized + 'static {
871 type Widget: StatefulWidget<State = Self>;
873
874 fn init_state(&mut self, _cx: &StateContext<Self>) {}
878
879 fn did_update_widget(&mut self, _old: &Self::Widget, _cx: &StateContext<Self>) {}
882
883 fn dispose(&mut self) {}
885
886 fn build(&self, cx: &StateContext<Self>) -> Box<dyn Widget>;
893}
894
895pub(crate) struct StateCell<S: State> {
896 state: RefCell<S>,
897 widget: RefCell<Rc<S::Widget>>,
898 mounted: Cell<bool>,
899}
900
901pub struct StateHandle<S: State> {
904 cell: Rc<StateCell<S>>,
905}
906
907impl<S: State> Clone for StateHandle<S> {
908 fn clone(&self) -> Self {
909 Self {
910 cell: self.cell.clone(),
911 }
912 }
913}
914
915impl<S: State> StateHandle<S> {
916 pub fn set_state(&self, change: impl FnOnce(&mut S)) {
921 if !self.cell.mounted.get() {
922 return;
923 }
924 match self.cell.state.try_borrow_mut() {
925 Ok(mut state) => change(&mut state),
926 Err(_) => panic!(
927 "set_state was called while the state was being read — from inside `build`, or \
928 from inside another `set_state`. Change state from a handler, not while building"
929 ),
930 }
931 request_rebuild();
932 }
933
934 pub fn read<R>(&self, read: impl FnOnce(&S) -> R) -> R {
936 read(&self.cell.state.borrow())
937 }
938
939 pub fn widget(&self) -> Rc<S::Widget> {
941 self.cell.widget.borrow().clone()
942 }
943
944 pub fn is_mounted(&self) -> bool {
946 self.cell.mounted.get()
947 }
948
949 pub fn callback(&self, change: impl Fn(&mut S) + 'static) -> Callback {
951 let handle = self.clone();
952 Callback::new(move || handle.set_state(|state| change(state)))
953 }
954
955 pub fn handler<T: Clone + 'static>(
962 &self,
963 change: impl Fn(&mut S, T) + 'static,
964 ) -> impl Fn(T) -> Callback + 'static {
965 let handle = self.clone();
966 let change = Rc::new(change);
967 move |value| {
968 let (handle, change) = (handle.clone(), change.clone());
969 Callback::new(move || handle.set_state(|state| change(state, value.clone())))
970 }
971 }
972}
973
974pub struct StateContext<'c, 'a, S: State> {
977 cx: &'c BuildContext<'a>,
978 cell: &'c Rc<StateCell<S>>,
979}
980
981impl<'a, S: State> Deref for StateContext<'_, 'a, S> {
982 type Target = BuildContext<'a>;
983
984 fn deref(&self) -> &BuildContext<'a> {
985 self.cx
986 }
987}
988
989impl<S: State> StateContext<'_, '_, S> {
990 pub fn widget(&self) -> Rc<S::Widget> {
992 self.cell.widget.borrow().clone()
993 }
994
995 pub fn handle(&self) -> StateHandle<S> {
997 StateHandle {
998 cell: self.cell.clone(),
999 }
1000 }
1001
1002 pub fn callback(&self, change: impl Fn(&mut S) + 'static) -> Callback {
1004 self.handle().callback(change)
1005 }
1006
1007 pub fn handler<T: Clone + 'static>(
1010 &self,
1011 change: impl Fn(&mut S, T) + 'static,
1012 ) -> impl Fn(T) -> Callback + 'static {
1013 self.handle().handler(change)
1014 }
1015}
1016
1017type Builder<Msg> = Box<dyn FnOnce(&BuildContext) -> Box<dyn Widget<Msg>>>;
1022
1023pub struct Component<Msg = Callback> {
1030 inner: LazyChild<Msg>,
1031 key: Option<u64>,
1032}
1033
1034impl Component<Callback> {
1035 pub fn stateless<W: StatelessWidget>(widget: W) -> Self {
1037 Self::from_builder(
1038 TypeId::of::<W>(),
1039 Box::new(move |cx: &BuildContext| widget.build(cx)),
1040 )
1041 }
1042
1043 pub fn stateful<W: StatefulWidget>(widget: W) -> Self {
1045 Self::from_builder(
1046 TypeId::of::<W>(),
1047 Box::new(move |cx: &BuildContext| {
1048 let Mounted {
1049 cell,
1050 hooks,
1051 previous,
1052 } = cx.runtime.states.mount(cx.key, Rc::new(widget));
1053 let _ = cx.hooks.set(hooks);
1054 let state_cx = StateContext { cx, cell: &cell };
1055 match previous {
1056 None => cell.state.borrow_mut().init_state(&state_cx),
1057 Some(old) => cell.state.borrow_mut().did_update_widget(&old, &state_cx),
1058 }
1059 let built = cell.state.borrow().build(&state_cx);
1060 built
1061 }),
1062 )
1063 }
1064}
1065
1066impl<Msg> Component<Msg> {
1067 fn from_builder(kind: TypeId, build: Builder<Msg>) -> Self {
1068 Self {
1069 inner: LazyChild {
1070 kind,
1071 build: RefCell::new(Some(build)),
1072 built: OnceCell::new(),
1073 unbuilt: Unbuilt,
1074 },
1075 key: None,
1076 }
1077 }
1078
1079 pub fn with_key(mut self, key: impl Hash) -> Self {
1082 let mut hasher = std::collections::hash_map::DefaultHasher::new();
1083 key.hash(&mut hasher);
1084 self.key = Some(hasher.finish());
1085 self
1086 }
1087
1088 fn restyle(&self, base: Style) -> Style {
1090 base
1091 }
1092}
1093
1094struct LazyChild<Msg> {
1096 kind: TypeId,
1097 build: RefCell<Option<Builder<Msg>>>,
1098 built: OnceCell<Box<dyn Widget<Msg>>>,
1099 unbuilt: Unbuilt,
1100}
1101
1102impl<Msg> LazyChild<Msg> {
1103 fn expand(&self, id: WidgetId, runtime: &Runtime, theme: &Theme) {
1106 if self.built.get().is_none() {
1107 let build = self.build.borrow_mut().take();
1108 if let Some(build) = build {
1109 let depth = DEPTH.with(|d| d.get());
1110 let cx = BuildContext::new((id, depth), self.kind, runtime, theme);
1111 let child = build(&cx);
1112 let _ = self.built.set(child);
1113 }
1114 }
1115 if let Some(child) = self.built.get() {
1116 let _deeper = Deeper::enter();
1117 child.expand(id, runtime, theme);
1118 }
1119 }
1120
1121 fn build_in(&self, id: WidgetId, runtime: &Runtime, theme: &Theme) {
1122 if let Some(child) = self.built.get() {
1123 child.build_in(id, runtime, theme);
1124 }
1125 }
1126
1127 fn build_themed(&self, theme: &Theme) {
1128 if let Some(child) = self.built.get() {
1129 child.build_themed(theme);
1130 }
1131 }
1132
1133 fn children(&self) -> &[Box<dyn Widget<Msg>>] {
1134 match self.built.get() {
1135 Some(child) => child.children(),
1136 None => {
1137 #[cfg(debug_assertions)]
1138 if self.build.borrow().is_some() {
1139 panic!(
1140 "a component's children were read before it was built: a traversal \
1141 reached this tree before the walk that builds components did. Call \
1142 `build_deferred(tree, &theme, &runtime)` on it first"
1143 );
1144 }
1145 &[]
1146 }
1147 }
1148 }
1149}
1150
1151impl<Msg> Deref for LazyChild<Msg> {
1152 type Target = dyn Widget<Msg>;
1153
1154 fn deref(&self) -> &(dyn Widget<Msg> + 'static) {
1155 match self.built.get() {
1156 Some(child) => child.as_ref(),
1157 None => &self.unbuilt,
1158 }
1159 }
1160}
1161
1162struct Unbuilt;
1164
1165impl<Msg> Widget<Msg> for Unbuilt {
1166 fn style(&self) -> Style {
1167 Style::default()
1168 }
1169
1170 fn children(&self) -> &[Box<dyn Widget<Msg>>] {
1171 &[]
1172 }
1173
1174 fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {}
1175
1176 fn on_click(&self) -> Option<Msg> {
1177 None
1178 }
1179
1180 fn debug_name(&self) -> &'static str {
1181 "Component"
1182 }
1183}
1184
1185crate::transparent::forward_transparent!(Component {
1186 fn key(&self) -> Option<u64> {
1190 self.key
1191 }
1192
1193 fn positioned(&self) -> Option<crate::positioned::Positioning> {
1194 self.inner.positioned()
1195 }
1196
1197 fn theme_override(
1198 &self,
1199 inherited: &crate::theme::Theme,
1200 ) -> Option<Box<crate::theme::Theme>> {
1201 self.inner.theme_override(inherited)
1202 }
1203
1204 fn media_override(&self, inherited: crate::MediaQuery) -> Option<crate::MediaQuery> {
1205 self.inner.media_override(inherited)
1206 }
1207
1208 fn scaffold_override(&self) -> Option<crate::ScaffoldInfo> {
1209 self.inner.scaffold_override()
1210 }
1211 fn scroll_behavior_override(&self) -> Option<crate::ScrollBehavior> {
1212 self.inner.scroll_behavior_override()
1213 }
1214
1215 fn autofill_group(&self) -> bool {
1216 self.inner.autofill_group()
1217 }
1218 fn area_toolbar(
1220 &self,
1221 context: crate::ToolbarContext,
1222 ) -> Option<Option<&dyn crate::widget::Widget<Msg>>> {
1223 self.inner.area_toolbar(context)
1224 }
1225});
1226
1227pub fn component(build: impl Fn(&BuildContext) -> Box<dyn Widget> + 'static) -> Component {
1240 Component::stateless(build)
1241}
1242
1243#[cfg(test)]
1244mod tests {
1245 use super::*;
1246 use crate::{build_deferred, keyed, Container, Flex};
1247 use frus_layout::Dimension;
1248
1249 type Builder = Rc<dyn Fn(&BuildContext) -> Box<dyn Widget>>;
1250 type Log = Rc<RefCell<Vec<String>>>;
1251 type Handle = Rc<RefCell<Option<StateHandle<LifeState>>>>;
1252
1253 fn log() -> Log {
1254 Rc::new(RefCell::new(Vec::new()))
1255 }
1256
1257 fn taken(log: &Log) -> Vec<String> {
1258 std::mem::take(&mut *log.borrow_mut())
1259 }
1260
1261 fn frame(runtime: &Runtime, root: &dyn Widget) {
1264 runtime.states.begin_build();
1265 build_deferred(root, &Theme::default(), runtime);
1266 runtime.states.end_frame();
1267 }
1268
1269 struct Life {
1271 tag: u32,
1272 log: Log,
1273 out: Handle,
1274 }
1275
1276 struct LifeState {
1277 log: Log,
1278 out: Handle,
1279 count: i32,
1280 }
1281
1282 impl StatefulWidget for Life {
1283 type State = LifeState;
1284 fn create_state(&self) -> LifeState {
1285 LifeState {
1286 log: self.log.clone(),
1287 out: self.out.clone(),
1288 count: 0,
1289 }
1290 }
1291 }
1292
1293 impl State for LifeState {
1294 type Widget = Life;
1295 fn init_state(&mut self, cx: &StateContext<Self>) {
1296 self.log
1297 .borrow_mut()
1298 .push(format!("init {}", cx.widget().tag));
1299 *self.out.borrow_mut() = Some(cx.handle());
1300 }
1301 fn did_update_widget(&mut self, old: &Life, cx: &StateContext<Self>) {
1302 self.log
1303 .borrow_mut()
1304 .push(format!("update {}->{}", old.tag, cx.widget().tag));
1305 }
1306 fn dispose(&mut self) {
1307 self.log.borrow_mut().push("dispose".to_string());
1308 }
1309 fn build(&self, _cx: &StateContext<Self>) -> Box<dyn Widget> {
1310 Box::new(Container::new().width(10.0 + self.count as f32).height(4.0))
1311 }
1312 }
1313
1314 fn life(tag: u32, log: &Log, out: &Handle) -> Component {
1315 Life {
1316 tag,
1317 log: log.clone(),
1318 out: out.clone(),
1319 }
1320 .into_widget()
1321 }
1322
1323 fn handle() -> Handle {
1324 Rc::new(RefCell::new(None))
1325 }
1326
1327 fn width(widget: &dyn Widget) -> Dimension {
1328 widget.style().width
1329 }
1330
1331 #[test]
1332 fn a_stateless_component_is_the_widget_it_builds() {
1333 let runtime = Runtime::default();
1334 let root = component(|_| Box::new(Container::new().width(120.0).height(30.0)));
1335 frame(&runtime, &root);
1336 assert_eq!(width(&root), Dimension::Length(120.0));
1337 assert_eq!(root.style().height, Dimension::Length(30.0));
1338 }
1339
1340 #[test]
1341 fn a_state_is_made_once_and_survives_rebuilds() {
1342 let (runtime, log, out) = (Runtime::default(), log(), handle());
1343 let first = life(1, &log, &out);
1344 frame(&runtime, &first);
1345 assert_eq!(taken(&log), ["init 1"]);
1346 assert_eq!(width(&first), Dimension::Length(10.0));
1347
1348 out.borrow().as_ref().unwrap().set_state(|s| s.count = 5);
1350 assert!(take_rebuild_request(), "set_state asks for a rebuild");
1351
1352 let second = life(2, &log, &out);
1353 frame(&runtime, &second);
1354 assert_eq!(
1355 taken(&log),
1356 ["update 1->2"],
1357 "the same place, the same kind: the state is kept and told what changed"
1358 );
1359 assert_eq!(
1360 width(&second),
1361 Dimension::Length(15.0),
1362 "and it is the kept one"
1363 );
1364 }
1365
1366 #[test]
1367 fn a_handler_carries_the_value_a_widget_reports_into_the_state() {
1368 let (runtime, log, out) = (Runtime::default(), log(), handle());
1369 frame(&runtime, &life(1, &log, &out));
1370 let handle = out.borrow().clone().unwrap();
1371 let on_value = handle.handler(|s, by: i32| s.count += by);
1372 let (three, four) = (on_value(3), on_value(4));
1373 assert_eq!(
1374 handle.read(|s| s.count),
1375 0,
1376 "nothing changes until it is delivered"
1377 );
1378 three.call();
1379 four.call();
1380 four.call();
1381 assert_eq!(
1382 handle.read(|s| s.count),
1383 11,
1384 "each delivery makes the change"
1385 );
1386 assert!(take_rebuild_request(), "and it asks for the rebuild");
1387 }
1388
1389 #[test]
1390 fn a_component_that_blocks_back_does_so_for_one_build_only() {
1391 let runtime = Runtime::default();
1392 let open = Rc::new(Cell::new(true));
1393 let asked = open.clone();
1394 let root = component(move |cx| {
1395 cx.block_back(asked.get());
1396 Box::new(Container::new().width(4.0).height(4.0))
1397 });
1398 frame(&runtime, &root);
1399 assert!(back_blocked(), "an open menu is in the way");
1400 open.set(false);
1401 frame(&runtime, &root);
1402 assert!(!back_blocked(), "each build says it afresh");
1403 }
1404
1405 #[test]
1406 fn a_state_is_disposed_when_a_rebuild_no_longer_reaches_it() {
1407 let (runtime, log, out) = (Runtime::default(), log(), handle());
1408 frame(&runtime, &Flex::column().child(life(1, &log, &out)));
1409 taken(&log);
1410 assert_eq!(runtime.states.len(), 1);
1411
1412 frame(&runtime, &Flex::column().child(Container::new()));
1413 assert_eq!(taken(&log), ["dispose"]);
1414 assert!(runtime.states.is_empty());
1415 let handle = out.borrow().clone().unwrap();
1416 assert!(!handle.is_mounted());
1417 take_rebuild_request();
1418 handle.set_state(|s| s.count = 9);
1419 assert!(
1420 !take_rebuild_request(),
1421 "a state that has left cannot ask for a rebuild"
1422 );
1423 }
1424
1425 #[test]
1426 fn a_different_kind_of_widget_in_the_same_place_starts_over() {
1427 let (runtime, log, out) = (Runtime::default(), log(), handle());
1428 frame(&runtime, &Flex::column().child(life(1, &log, &out)));
1429 taken(&log);
1430 let other = component(|cx| {
1432 cx.use_state(|| 0u8);
1433 Box::new(Container::new())
1434 });
1435 frame(&runtime, &Flex::column().child(other));
1436 assert_eq!(
1437 taken(&log),
1438 ["dispose"],
1439 "the old kind is disposed, not reused"
1440 );
1441 }
1442
1443 #[test]
1444 fn a_key_carries_the_state_wherever_the_siblings_go() {
1445 let (runtime, log) = (Runtime::default(), log());
1446 let (a, b) = (handle(), handle());
1447 let column = |order: [u32; 2]| {
1448 let mut column = Flex::column();
1449 for tag in order {
1450 let out = if tag == 1 { &a } else { &b };
1451 column = column.child(keyed(tag, life(tag, &log, out)));
1452 }
1453 column
1454 };
1455 frame(&runtime, &column([1, 2]));
1456 a.borrow().as_ref().unwrap().set_state(|s| s.count = 1);
1457 b.borrow().as_ref().unwrap().set_state(|s| s.count = 2);
1458 taken(&log);
1459
1460 frame(&runtime, &column([2, 1]));
1461 let counts = |h: &Handle| h.borrow().as_ref().unwrap().read(|s| s.count);
1462 assert_eq!(
1463 (counts(&a), counts(&b)),
1464 (1, 2),
1465 "each state stayed with its key"
1466 );
1467 assert_eq!(runtime.states.len(), 2);
1468 assert!(
1469 !taken(&log)
1470 .iter()
1471 .any(|line| line == "dispose" || line.starts_with("init")),
1472 "and neither was made again or let go"
1473 );
1474 }
1475
1476 #[test]
1477 fn a_component_that_builds_a_component_keeps_both_states() {
1478 let (runtime, log, out) = (Runtime::default(), log(), handle());
1479 let (inner_log, inner_out) = (log.clone(), out.clone());
1480 let outer = component(move |cx| {
1481 cx.use_state(|| 0);
1482 Box::new(life(7, &inner_log, &inner_out))
1484 });
1485 frame(&runtime, &outer);
1486 assert_eq!(
1487 runtime.states.len(),
1488 2,
1489 "one for the hooks, one for the state"
1490 );
1491 assert_eq!(taken(&log), ["init 7"]);
1492 }
1493
1494 #[test]
1495 fn hooks_keep_their_values_between_builds() {
1496 let runtime = Runtime::default();
1497 let seen = Rc::new(RefCell::new(Vec::new()));
1498 let build = {
1500 let seen = seen.clone();
1501 Rc::new(move |cx: &BuildContext| -> Box<dyn Widget> {
1502 let taps = cx.use_state(|| 10);
1503 let scratch = cx.use_ref(|| 0);
1504 scratch.update(|n| *n += 1);
1505 taps.update(|n| *n += 1);
1506 seen.borrow_mut().push((taps.get(), scratch.get()));
1507 Box::new(Container::new())
1508 })
1509 };
1510 let make = |build: &Builder| {
1511 let build = build.clone();
1512 Component::stateless(move |cx: &BuildContext| build(cx))
1513 };
1514 let build: Builder = build;
1515 frame(&runtime, &make(&build));
1516 assert!(
1517 take_rebuild_request(),
1518 "changing a state hook asks for a rebuild"
1519 );
1520 frame(&runtime, &make(&build));
1521 frame(&runtime, &make(&build));
1522 assert_eq!(*seen.borrow(), [(11, 1), (12, 2), (13, 3)]);
1523 }
1524
1525 #[test]
1526 fn use_memo_recomputes_only_when_its_dependencies_change() {
1527 let runtime = Runtime::default();
1528 let computed = Rc::new(Cell::new(0));
1529 let dep = Rc::new(Cell::new(1));
1530 let build: Builder = {
1531 let (computed, dep) = (computed.clone(), dep.clone());
1532 Rc::new(move |cx| {
1533 let d = dep.get();
1534 let value = cx.use_memo(d, || {
1535 computed.set(computed.get() + 1);
1536 d * 100
1537 });
1538 Box::new(Container::new().width(*value as f32))
1539 })
1540 };
1541 let make = || {
1542 let build = build.clone();
1543 Component::stateless(move |cx: &BuildContext| build(cx))
1544 };
1545 let root = make();
1546 frame(&runtime, &root);
1547 assert_eq!(
1548 (computed.get(), width(&root)),
1549 (1, Dimension::Length(100.0))
1550 );
1551 let root = make();
1552 frame(&runtime, &root);
1553 assert_eq!(computed.get(), 1, "the same dependency: not computed again");
1554 dep.set(2);
1555 let root = make();
1556 frame(&runtime, &root);
1557 assert_eq!(
1558 (computed.get(), width(&root)),
1559 (2, Dimension::Length(200.0))
1560 );
1561 }
1562
1563 #[test]
1564 fn effects_run_after_the_build_and_clean_up_before_the_next_and_at_the_end() {
1565 let (runtime, log) = (Runtime::default(), log());
1566 let dep = Rc::new(Cell::new(1));
1567 let build: Builder = {
1568 let (log, dep) = (log.clone(), dep.clone());
1569 Rc::new(move |cx| {
1570 let d = dep.get();
1571 let log = log.clone();
1572 log.borrow_mut().push(format!("build {d}"));
1573 cx.use_effect(d, move || {
1574 log.borrow_mut().push(format!("effect {d}"));
1575 move || log.borrow_mut().push(format!("cleanup {d}"))
1576 });
1577 Box::new(Container::new())
1578 })
1579 };
1580 let make = || {
1581 let build = build.clone();
1582 Component::stateless(move |cx: &BuildContext| build(cx))
1583 };
1584 frame(&runtime, &make());
1585 assert_eq!(
1586 taken(&log),
1587 ["build 1", "effect 1"],
1588 "after the build, not during it"
1589 );
1590
1591 frame(&runtime, &make());
1592 assert_eq!(
1593 taken(&log),
1594 ["build 1"],
1595 "the same dependency: nothing runs"
1596 );
1597
1598 dep.set(2);
1599 frame(&runtime, &make());
1600 assert_eq!(
1601 taken(&log),
1602 ["build 2", "cleanup 1", "effect 2"],
1603 "the old one is undone before the new one starts"
1604 );
1605
1606 frame(&runtime, &Flex::column().child(Container::new()));
1608 assert_eq!(taken(&log), ["cleanup 2"]);
1609 }
1610
1611 #[test]
1612 #[should_panic(expected = "different order")]
1613 fn hooks_called_in_a_different_order_are_caught() {
1614 let runtime = Runtime::default();
1615 let first = Rc::new(Cell::new(true));
1616 let build: Builder = {
1617 let first = first.clone();
1618 Rc::new(move |cx| {
1619 if first.get() {
1620 cx.use_state(|| 1i32);
1621 }
1622 cx.use_ref(String::new);
1623 Box::new(Container::new())
1624 })
1625 };
1626 let make = || {
1627 let build = build.clone();
1628 Component::stateless(move |cx: &BuildContext| build(cx))
1629 };
1630 frame(&runtime, &make());
1631 first.set(false);
1632 frame(&runtime, &make());
1633 }
1634
1635 #[test]
1636 #[should_panic(expected = "set_state was called while")]
1637 fn changing_state_from_inside_build_is_refused() {
1638 struct Greedy;
1639 struct GreedyState;
1640 impl StatefulWidget for Greedy {
1641 type State = GreedyState;
1642 fn create_state(&self) -> GreedyState {
1643 GreedyState
1644 }
1645 }
1646 impl State for GreedyState {
1647 type Widget = Greedy;
1648 fn build(&self, cx: &StateContext<Self>) -> Box<dyn Widget> {
1649 cx.handle().set_state(|_| {});
1650 Box::new(Container::new())
1651 }
1652 }
1653 frame(&Runtime::default(), &Greedy.into_widget());
1654 }
1655
1656 #[test]
1657 fn an_interval_is_asked_for_by_each_build_that_wants_it() {
1658 let runtime = Runtime::default();
1659 let on = Rc::new(Cell::new(true));
1660 let build: Builder = {
1661 let on = on.clone();
1662 Rc::new(move |cx| {
1663 if on.get() {
1664 cx.use_interval(std::time::Duration::from_secs(1), || {});
1665 cx.use_interval(std::time::Duration::from_secs(5), || {});
1666 }
1667 Box::new(Container::new())
1668 })
1669 };
1670 let make = || {
1671 let build = build.clone();
1672 Component::stateless(move |cx: &BuildContext| build(cx))
1673 };
1674 frame(&runtime, &make());
1675 let first = intervals();
1676 assert_eq!(first.len(), 2);
1677 assert_ne!(first[0].id, first[1].id, "two timers are told apart");
1678
1679 frame(&runtime, &make());
1680 let second = intervals();
1681 assert_eq!(
1682 first.iter().map(|i| i.id).collect::<Vec<_>>(),
1683 second.iter().map(|i| i.id).collect::<Vec<_>>(),
1684 "and each is the same timer from one build to the next"
1685 );
1686
1687 on.set(false);
1688 frame(&runtime, &make());
1689 assert!(
1690 intervals().is_empty(),
1691 "a build that stops asking stops the timer"
1692 );
1693 }
1694
1695 #[test]
1696 fn a_service_provided_to_the_runtime_is_reachable_from_a_build() {
1697 let runtime = Runtime::default();
1698 runtime.states.provide(Rc::new(41u32));
1699 let root = component(|cx| {
1700 let n = cx.service::<u32>().expect("provided");
1701 Box::new(Container::new().width(*n as f32 + 1.0))
1702 });
1703 frame(&runtime, &root);
1704 assert_eq!(width(&root), Dimension::Length(42.0));
1705 assert!(runtime.states.service::<String>().is_none());
1706 }
1707}