cranpose_core/event_dispatch.rs
1use crate::{snapshot_v2, thread_state};
2
3/// Runs the provided closure inside a mutable snapshot and applies the result.
4///
5/// Use this function when you need to update `MutableState` from outside the
6/// composition or layout phase, typically in event handlers or async tasks.
7///
8/// # Why is this needed?
9/// Cranpose uses a snapshot system (MVCC) to isolate state changes. Modifications
10/// made to `MutableState` are only visible to the current thread's active snapshot.
11/// To make changes visible to the rest of the system (and trigger recomposition),
12/// they must be "applied" by committing the snapshot. This helper handles that
13/// lifecycle for you.
14///
15/// # Example
16///
17/// ```ignore
18/// // Inside a button click handler
19/// run_in_mutable_snapshot(|| {
20/// count.set(count.value() + 1);
21/// });
22/// ```
23///
24/// # Important
25/// ALL UI event handlers (keyboard, mouse, touch, animations) that modify state
26/// MUST use this function or [`dispatch_ui_event`].
27pub fn run_in_mutable_snapshot<T>(block: impl FnOnce() -> T) -> Result<T, &'static str> {
28 let snapshot = snapshot_v2::take_mutable_snapshot(None, None);
29
30 let _applied_guard = AppliedSnapshotFlagGuard::enter();
31 let value = snapshot.enter(block);
32
33 match snapshot.apply() {
34 snapshot_v2::SnapshotApplyResult::Success => Ok(value),
35 snapshot_v2::SnapshotApplyResult::Failure => Err("Snapshot apply failed"),
36 }
37}
38
39struct AppliedSnapshotFlagGuard {
40 previous: bool,
41}
42
43impl AppliedSnapshotFlagGuard {
44 fn enter() -> Self {
45 let previous = thread_state::cells(|cells| cells.in_applied_snapshot.replace(true));
46 Self { previous }
47 }
48}
49
50impl Drop for AppliedSnapshotFlagGuard {
51 fn drop(&mut self) {
52 thread_state::cells(|cells| cells.in_applied_snapshot.set(self.previous));
53 }
54}
55
56/// Dispatches a UI event in a proper snapshot context.
57///
58/// This is a convenience wrapper around [`run_in_mutable_snapshot`] that returns
59/// `Option<T>` instead of `Result<T, &str>`.
60///
61/// # Example
62/// ```ignore
63/// // In a keyboard event handler:
64/// dispatch_ui_event(|| {
65/// text_field_state.edit(|buffer| {
66/// buffer.insert("a");
67/// });
68/// });
69/// ```
70pub fn dispatch_ui_event<T>(block: impl FnOnce() -> T) -> Option<T> {
71 run_in_mutable_snapshot(block).ok()
72}
73
74/// Ends the event handler scope when dropped, restoring the setting that held before it.
75#[must_use]
76pub struct EventHandlerScopeGuard {
77 previous: bool,
78}
79
80impl Drop for EventHandlerScopeGuard {
81 fn drop(&mut self) {
82 thread_state::cells(|cells| cells.in_event_handler.set(self.previous));
83 }
84}
85
86/// Marks the thread as inside an event handler until the returned guard is dropped. In debug
87/// builds, a state write there that is not inside a snapshot logs a warning.
88pub fn enter_event_handler_scope() -> EventHandlerScopeGuard {
89 let previous = thread_state::cells(|cells| cells.in_event_handler.replace(true));
90 EventHandlerScopeGuard { previous }
91}
92
93/// Returns true if currently in an event handler context.
94pub fn in_event_handler() -> bool {
95 thread_state::cells(|cells| cells.in_event_handler.get())
96}
97
98/// Returns true if currently in an applied snapshot context.
99pub fn in_applied_snapshot() -> bool {
100 thread_state::cells(|cells| cells.in_applied_snapshot.get())
101}