1use rustc_hash::{FxHashMap, FxHashSet};
2use std::any::TypeId;
3use std::cell::{Cell, RefCell};
4use std::marker::PhantomData;
5use std::sync::Arc;
6use web_time::Instant;
7
8use crate::app::context::SurfaceMode;
9use crate::app::input::command_registry::CommandEntry;
10use crate::app::input::command_registry::CommandRegistry;
11use crate::callback::{CancellationToken, CommandLink, CommandTx, Dispatcher, Link, ScopeId};
12use crate::core::context_value::ContextValue;
13use crate::core::element::{Element, Key};
14use crate::core::event::KeyEvent;
15use crate::core::node::{NodeId, NodeKind, NodeTree};
16use crate::core::runtime_env::{
17 CopyFeedbackRequest, DevToolsRequest, MemoDependency, MemoDependencySnapshot, RuntimeEnv,
18 ScrollDependency, ScrollDependencyKind, ScrollIdentity, TranscriptEntry,
19};
20use crate::runtime::FocusRequest;
21use crate::style::{HostTerminalColors, Rect, RichText, Theme, ThemeExtension};
22use crate::utils::GridSelection;
23
24#[non_exhaustive]
29pub struct Command {
30 action: Box<dyn CommandAction>,
31}
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub enum TaskPolicy {
36 QueueAll,
38 DropIfRunning,
40 LatestOnly,
42}
43
44mod task_policy;
45
46use task_policy::Task;
47
48#[cfg(not(target_arch = "wasm32"))]
49mod executor_native;
50#[cfg(target_arch = "wasm32")]
51mod executor_wasm;
52#[cfg(not(target_arch = "wasm32"))]
53mod timer_native;
54#[cfg(target_arch = "wasm32")]
55mod timer_wasm;
56
57#[cfg(not(target_arch = "wasm32"))]
58use executor_native::TaskExecutor;
59#[cfg(target_arch = "wasm32")]
60use executor_wasm::TaskExecutor;
61#[cfg(not(target_arch = "wasm32"))]
62use timer_native::TimerService;
63#[cfg(target_arch = "wasm32")]
64use timer_wasm::TimerService;
65
66pub(crate) fn advance_deferred_commands(horizon: Instant, runtime_id: RuntimeId) -> usize {
72 TimerService::global().advance_owned(horizon, runtime_id)
73}
74
75impl std::fmt::Debug for Command {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 f.debug_struct("Command").finish_non_exhaustive()
78 }
79}
80
81impl Command {
82 pub fn new(action: impl FnOnce() + 'static) -> Self {
84 Self {
85 action: Box::new(RunAction(Some(action))),
86 }
87 }
88
89 pub fn spawn<Msg, F>(f: F) -> Self
91 where
92 Msg: Send + 'static,
93 F: FnOnce(CommandLink<Msg>) + Send + 'static,
94 {
95 Self {
96 action: Box::new(SpawnAction::<Msg, F> {
97 f: Some(f),
98 _marker: PhantomData,
99 }),
100 }
101 }
102
103 pub fn after<Msg, F>(delay: std::time::Duration, f: F) -> Self
126 where
127 Msg: Send + 'static,
128 F: FnOnce(CommandLink<Msg>) + Send + 'static,
129 {
130 Self {
131 action: Box::new(AfterAction::<Msg, F> {
132 delay,
133 f: Some(f),
134 _marker: PhantomData,
135 }),
136 }
137 }
138
139 pub fn spawn_keyed<Msg, F>(key: impl Into<Arc<str>>, policy: TaskPolicy, f: F) -> Self
141 where
142 Msg: Send + 'static,
143 F: FnOnce(CommandLink<Msg>) + Send + 'static,
144 {
145 Self {
146 action: Box::new(SpawnKeyedAction::<Msg, F> {
147 key: key.into(),
148 policy,
149 f: Some(f),
150 _marker: PhantomData,
151 }),
152 }
153 }
154
155 pub(crate) fn run(self, runtime: CommandRuntime) {
156 self.action.run(runtime);
157 }
158}
159
160pub(crate) struct CommandRuntime {
161 pub(crate) scope: ScopeId,
162 pub(crate) tx: CommandTx,
163 pub(crate) runtime_id: RuntimeId,
165}
166
167#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
172pub(crate) struct RuntimeId(u64);
173
174impl RuntimeId {
175 #[cfg(test)]
176 pub(crate) fn from_raw_for_tests(raw: u64) -> Self {
177 Self(raw)
178 }
179
180 pub(crate) fn next() -> Self {
182 static NEXT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
183 Self(NEXT.fetch_add(1, std::sync::atomic::Ordering::Relaxed))
184 }
185}
186
187trait CommandAction {
188 fn run(self: Box<Self>, runtime: CommandRuntime);
189}
190
191struct RunAction<F>(Option<F>);
192
193impl<F> CommandAction for RunAction<F>
194where
195 F: FnOnce() + 'static,
196{
197 fn run(mut self: Box<Self>, _runtime: CommandRuntime) {
198 if let Some(f) = self.0.take() {
199 f();
200 }
201 }
202}
203
204struct SpawnAction<Msg, F> {
205 f: Option<F>,
206 _marker: PhantomData<fn(Msg)>,
207}
208
209struct SpawnKeyedAction<Msg, F> {
210 key: Arc<str>,
211 policy: TaskPolicy,
212 f: Option<F>,
213 _marker: PhantomData<fn(Msg)>,
214}
215
216pub(crate) fn schedule_after(delay: std::time::Duration, f: impl FnOnce() + Send + 'static) {
221 TimerService::global().schedule(delay, Task::with_token(f, CancellationToken::default()));
222}
223
224struct AfterAction<Msg, F> {
225 delay: std::time::Duration,
226 f: Option<F>,
227 _marker: PhantomData<fn(Msg)>,
228}
229
230impl<Msg, F> CommandAction for AfterAction<Msg, F>
231where
232 Msg: Send + 'static,
233 F: FnOnce(CommandLink<Msg>) + Send + 'static,
234{
235 fn run(mut self: Box<Self>, runtime: CommandRuntime) {
236 let Some(f) = self.f.take() else {
237 return;
238 };
239
240 let token = CancellationToken::default();
241 let link = CommandLink::new(runtime.scope, runtime.tx, token.clone());
242 TimerService::global().schedule_owned(
243 self.delay,
244 Task::with_token(move || f(link), token),
245 Some(runtime.runtime_id),
246 );
247 }
248}
249
250impl<Msg, F> CommandAction for SpawnAction<Msg, F>
251where
252 Msg: Send + 'static,
253 F: FnOnce(CommandLink<Msg>) + Send + 'static,
254{
255 fn run(mut self: Box<Self>, runtime: CommandRuntime) {
256 let Some(f) = self.f.take() else {
257 return;
258 };
259
260 let token = CancellationToken::default();
261 let link = CommandLink::new(runtime.scope, runtime.tx, token.clone());
262 TaskExecutor::global().execute(Task::with_token(move || f(link), token));
263 }
264}
265
266impl<Msg, F> CommandAction for SpawnKeyedAction<Msg, F>
267where
268 Msg: Send + 'static,
269 F: FnOnce(CommandLink<Msg>) + Send + 'static,
270{
271 fn run(mut self: Box<Self>, runtime: CommandRuntime) {
272 let Some(f) = self.f.take() else {
273 return;
274 };
275
276 let key = Arc::clone(&self.key);
277 let policy = self.policy;
278 let token = CancellationToken::default();
279 let link = CommandLink::new(runtime.scope, runtime.tx, token.clone());
280 TaskExecutor::global().execute_keyed(key, policy, Task::with_token(move || f(link), token));
281 }
282}
283
284impl<Msg: 'static> Link<Msg> {
285 pub fn command<F>(&self, f: F) -> Command
287 where
288 Msg: Send + 'static,
289 F: FnOnce(CommandLink<Msg>) + Send + 'static,
290 {
291 Command::spawn::<Msg, F>(f)
292 }
293
294 pub fn command_keyed<F>(&self, key: impl Into<Arc<str>>, policy: TaskPolicy, f: F) -> Command
296 where
297 Msg: Send + 'static,
298 F: FnOnce(CommandLink<Msg>) + Send + 'static,
299 {
300 Command::spawn_keyed::<Msg, F>(key, policy, f)
301 }
302}
303
304#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
311pub enum UpdateLevel {
312 #[default]
314 None,
315 Paint,
317 Layout,
319 Full,
321}
322
323pub struct Update {
325 pub dirty: bool,
327 pub(crate) level: UpdateLevel,
329 pub command: Option<Command>,
331}
332
333impl Update {
334 pub fn paint() -> Self {
336 Self {
337 dirty: true,
338 level: UpdateLevel::Paint,
339 command: None,
340 }
341 }
342
343 pub fn layout() -> Self {
345 Self {
346 dirty: true,
347 level: UpdateLevel::Layout,
348 command: None,
349 }
350 }
351
352 pub fn layout_with_command(command: impl Into<Option<Command>>) -> Self {
354 match command.into() {
355 Some(command) => Self {
356 dirty: true,
357 level: UpdateLevel::Layout,
358 command: Some(command),
359 },
360 None => Self::layout(),
361 }
362 }
363
364 pub fn full() -> Self {
366 Self {
367 dirty: true,
368 level: UpdateLevel::Full,
369 command: None,
370 }
371 }
372
373 pub fn command_only(command: Command) -> Self {
375 Self {
376 dirty: false,
377 level: UpdateLevel::None,
378 command: Some(command),
379 }
380 }
381
382 pub fn none() -> Self {
384 Self {
385 dirty: false,
386 level: UpdateLevel::None,
387 command: None,
388 }
389 }
390
391 pub fn with_command(command: impl Into<Option<Command>>) -> Self {
393 match command.into() {
394 Some(cmd) => Self {
395 dirty: true,
396 level: UpdateLevel::Full,
397 command: Some(cmd),
398 },
399 None => Self::full(),
400 }
401 }
402
403 pub fn level(&self) -> UpdateLevel {
405 self.level
406 }
407}
408
409pub struct KeyUpdate {
413 pub handled: bool,
415 pub update: Update,
417}
418
419impl KeyUpdate {
420 pub fn handled(update: Update) -> Self {
422 Self {
423 handled: true,
424 update,
425 }
426 }
427
428 pub fn unhandled(update: Update) -> Self {
430 Self {
431 handled: false,
432 update,
433 }
434 }
435}
436
437pub trait Component: Sized + 'static {
442 type Message: 'static;
444
445 type Properties: Clone + PartialEq + 'static;
447
448 type State: 'static;
450
451 fn create_state(&self, props: &Self::Properties) -> Self::State;
453
454 fn memo_key(&self, _props: &Self::Properties, _ctx: &Context<Self>) -> Option<u64> {
460 None
461 }
462
463 fn init(&mut self, _ctx: &mut Context<Self>) -> Option<Command> {
467 None
468 }
469
470 fn view(&self, ctx: &Context<Self>) -> Element;
472
473 fn on_key(&mut self, _key: KeyEvent, _ctx: &mut Context<Self>) -> KeyUpdate {
478 KeyUpdate::unhandled(Update::none())
479 }
480
481 fn on_window_focus_changed(&mut self, _focused: bool, _ctx: &mut Context<Self>) -> Update {
485 Update::none()
486 }
487
488 fn update(&mut self, msg: Self::Message, ctx: &mut Context<Self>) -> Update;
492
493 fn on_props_changed(
497 &mut self,
498 _old_props: &Self::Properties,
499 _ctx: &mut Context<Self>,
500 ) -> Update {
501 Update::none()
502 }
503
504 fn unmount(&mut self, _ctx: &mut Context<Self>) {}
506}
507
508#[derive(Clone, Copy, Debug, PartialEq, Eq)]
510pub enum Breakpoint {
511 Small,
513 Medium,
515 Large,
517}
518
519#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
521pub struct ScrollbarVisibility {
522 pub v: bool,
524 pub h: bool,
526}
527
528#[derive(Default)]
530struct NodeChainState {
531 current_node: Cell<Option<NodeId>>,
532 chain_scopes: RefCell<Vec<ScopeId>>,
533 chain_keys: RefCell<Vec<Key>>,
534 generation: Cell<u64>,
535}
536
537impl NodeChainState {
538 fn node_id(&self) -> Option<NodeId> {
539 self.current_node.get()
540 }
541
542 fn has_within_scope(&self, scope: ScopeId) -> bool {
543 self.chain_scopes.borrow().contains(&scope)
544 }
545
546 fn has_within_key(&self, key: &Key) -> bool {
547 self.chain_keys
548 .borrow()
549 .iter()
550 .any(|candidate| candidate == key)
551 }
552
553 fn push_scope_if_missing(scopes: &mut Vec<ScopeId>, scope: ScopeId) {
554 if !scopes.contains(&scope) {
555 scopes.push(scope);
556 }
557 }
558
559 fn push_key_if_missing(keys: &mut Vec<Key>, key: &Key) {
560 if !keys.iter().any(|candidate| candidate == key) {
561 keys.push(key.clone());
562 }
563 }
564
565 fn replace_snapshot(&self, node: Option<NodeId>, scopes: Vec<ScopeId>, keys: Vec<Key>) {
566 let mut scope_ref = self.chain_scopes.borrow_mut();
567 let mut key_ref = self.chain_keys.borrow_mut();
568 let changed = self.current_node.get() != node || *scope_ref != scopes || *key_ref != keys;
569
570 self.current_node.set(node);
571 *scope_ref = scopes;
572 *key_ref = keys;
573
574 if changed {
575 self.generation
576 .set(self.generation.get().wrapping_add(1).max(1));
577 }
578 }
579
580 fn chain_for(tree: &NodeTree, node: Option<NodeId>) -> (Vec<ScopeId>, Vec<Key>) {
582 let mut scopes = Vec::new();
583 let mut keys = Vec::new();
584
585 if let Some(mut cur) = node {
586 Self::push_scope_if_missing(&mut scopes, ScopeId(1));
587
588 loop {
589 if !tree.is_valid(cur) {
590 break;
591 }
592
593 let node_ref = tree.node(cur);
594
595 if let Some(k) = &node_ref.key {
596 Self::push_key_if_missing(&mut keys, k);
597 }
598
599 if let NodeKind::Group(group) = &node_ref.kind {
600 Self::push_scope_if_missing(&mut scopes, group.scope);
601 }
602
603 let Some(parent) = node_ref.parent else {
604 break;
605 };
606 cur = parent;
607 }
608 }
609
610 (scopes, keys)
611 }
612
613 fn update_chain(&self, tree: &NodeTree, node: Option<NodeId>) {
615 let (scopes, keys) = Self::chain_for(tree, node);
616 self.replace_snapshot(node, scopes, keys);
617 }
618
619 fn generation(&self) -> u64 {
620 self.generation.get()
621 }
622}
623
624#[derive(Default)]
626pub(crate) struct FocusContext {
627 inner: NodeChainState,
628}
629
630impl FocusContext {
631 pub(crate) fn update_from_tree(
632 &self,
633 tree: &NodeTree,
634 focused: Option<NodeId>,
635 focused_key: Option<&Key>,
636 ) {
637 let mut cur = focused.filter(|id| tree.is_valid(*id));
638
639 if cur.is_none()
640 && let Some(key) = focused_key
641 {
642 if let Some(id) = tree
643 .iter()
644 .find(|n| n.key.as_ref() == Some(key))
645 .map(|n| n.id)
646 {
647 cur = Some(id);
648 } else {
649 self.inner
650 .replace_snapshot(None, Vec::new(), vec![key.clone()]);
651 return;
652 }
653 }
654
655 self.inner.update_chain(tree, cur);
656 }
657
658 pub(crate) fn focused_node_id(&self) -> Option<NodeId> {
659 self.inner.node_id()
660 }
661
662 pub(crate) fn has_focus_within_scope(&self, scope: ScopeId) -> bool {
663 self.inner.has_within_scope(scope)
664 }
665
666 pub(crate) fn has_focus_within_key(&self, key: &Key) -> bool {
667 self.inner.has_within_key(key)
668 }
669
670 pub(crate) fn generation(&self) -> u64 {
671 self.inner.generation()
672 }
673}
674
675#[derive(Default)]
683pub(crate) struct HoverContext {
684 inner: NodeChainState,
685 queries: RefCell<Vec<(ScopeId, HoverQuery)>>,
688}
689
690#[derive(Clone, PartialEq, Eq)]
692enum HoverQuery {
693 WithinScope(ScopeId),
695 WithinKey(Key),
697 NodeId,
699}
700
701impl HoverContext {
702 pub(crate) fn update_from_tree(&self, tree: &NodeTree, hovered: Option<NodeId>) {
703 self.queries.borrow_mut().clear();
707 self.update_chain(tree, hovered);
708 }
709
710 pub(crate) fn update_chain(&self, tree: &NodeTree, hovered: Option<NodeId>) {
717 let cur = hovered.filter(|id| tree.is_valid(*id));
718 self.inner.update_chain(tree, cur);
719 }
720
721 pub(crate) fn begin_scoped_view(
728 &self,
729 tree: &NodeTree,
730 hovered: Option<NodeId>,
731 scopes: &[ScopeId],
732 ) {
733 self.queries
734 .borrow_mut()
735 .retain(|(asker, _)| !scopes.contains(asker));
736 self.update_chain(tree, hovered);
737 }
738
739 fn record(&self, asker: ScopeId, query: HoverQuery) {
740 let mut queries = self.queries.borrow_mut();
741 if !queries
742 .iter()
743 .any(|(scope, recorded)| *scope == asker && *recorded == query)
744 {
745 queries.push((asker, query));
746 }
747 }
748
749 pub(crate) fn hovered_node_id(&self, asker: ScopeId) -> Option<NodeId> {
750 self.record(asker, HoverQuery::NodeId);
751 self.inner.node_id()
752 }
753
754 pub(crate) fn has_hover_within_scope(&self, scope: ScopeId) -> bool {
755 self.record(scope, HoverQuery::WithinScope(scope));
756 self.inner.has_within_scope(scope)
757 }
758
759 pub(crate) fn has_hover_within_key(&self, asker: ScopeId, key: &Key) -> bool {
760 self.record(asker, HoverQuery::WithinKey(key.clone()));
761 self.inner.has_within_key(key)
762 }
763
764 pub(crate) fn scopes_needing_view(
771 &self,
772 tree: &NodeTree,
773 hovered: Option<NodeId>,
774 ) -> Vec<ScopeId> {
775 let queries = self.queries.borrow();
776 if queries.is_empty() {
777 return Vec::new();
778 }
779 let cur = hovered.filter(|id| tree.is_valid(*id));
780 let (scopes, keys) = NodeChainState::chain_for(tree, cur);
781
782 let mut affected = Vec::new();
783 for (asker, query) in queries.iter() {
784 let changed = match query {
785 HoverQuery::WithinScope(scope) => {
786 scopes.contains(scope) != self.inner.has_within_scope(*scope)
787 }
788 HoverQuery::WithinKey(key) => {
789 keys.iter().any(|candidate| candidate == key) != self.inner.has_within_key(key)
790 }
791 HoverQuery::NodeId => self.inner.node_id() != cur,
792 };
793 if changed && !affected.contains(asker) {
794 affected.push(*asker);
795 }
796 }
797 affected
798 }
799
800 pub(crate) fn generation(&self) -> u64 {
801 self.inner.generation()
802 }
803}
804
805#[derive(Default)]
807pub(crate) struct ScrollContext {
808 by_key: RefCell<FxHashMap<ScrollIdentity, ScrollbarVisibility>>,
809 text_area_metrics_by_key: RefCell<FxHashMap<ScrollIdentity, crate::widgets::TextAreaMetrics>>,
810 metrics_generations: RefCell<FxHashMap<ScrollIdentity, u64>>,
811 scrollbar_generations: RefCell<FxHashMap<ScrollIdentity, u64>>,
812 metrics_view_dependencies: RefCell<FxHashSet<ScrollIdentity>>,
813 scrollbar_view_dependencies: RefCell<FxHashSet<ScrollIdentity>>,
814}
815
816#[derive(Clone, Debug, PartialEq, Eq)]
819pub(crate) struct ScrollGenerations {
820 metrics: FxHashMap<ScrollIdentity, u64>,
821 scrollbars: FxHashMap<ScrollIdentity, u64>,
822}
823
824impl ScrollContext {
825 pub(crate) fn update_from_tree(&self, tree: &NodeTree) {
826 let mut map = self.by_key.borrow_mut();
827 let prev = std::mem::take(&mut *map);
828 let mut metrics_map = self.text_area_metrics_by_key.borrow_mut();
829 let prev_metrics = std::mem::take(&mut *metrics_map);
830
831 for node in tree.iter_with_overlays() {
832 if let (Some(key), NodeKind::TextArea(text_area)) = (&node.key, &node.kind) {
833 let identity = ScrollIdentity {
834 scope: node_scope(tree, node.id),
835 key: key.clone(),
836 };
837 let metrics = text_area.metrics(node.rect);
838 map.insert(identity.clone(), metrics.scrollbars);
839 metrics_map.insert(identity, metrics);
840 }
841 }
842
843 advance_changed_generations(&prev, &map, &self.scrollbar_generations);
844 advance_changed_generations(&prev_metrics, &metrics_map, &self.metrics_generations);
845 }
846
847 pub(crate) fn get(&self, identity: &ScrollIdentity) -> Option<ScrollbarVisibility> {
848 self.by_key.borrow().get(identity).copied()
849 }
850
851 pub(crate) fn text_area_metrics(
852 &self,
853 identity: &ScrollIdentity,
854 ) -> Option<crate::widgets::TextAreaMetrics> {
855 self.text_area_metrics_by_key
856 .borrow()
857 .get(identity)
858 .cloned()
859 }
860
861 pub(crate) fn begin_view(&self, scope: ScopeId) {
862 self.metrics_view_dependencies
863 .borrow_mut()
864 .retain(|identity| identity.scope != scope);
865 self.scrollbar_view_dependencies
866 .borrow_mut()
867 .retain(|identity| identity.scope != scope);
868 }
869
870 pub(crate) fn remove_scope(&self, scope: ScopeId) {
871 self.begin_view(scope);
872 self.by_key
873 .borrow_mut()
874 .retain(|identity, _| identity.scope != scope);
875 self.text_area_metrics_by_key
876 .borrow_mut()
877 .retain(|identity, _| identity.scope != scope);
878 self.metrics_generations
879 .borrow_mut()
880 .retain(|identity, _| identity.scope != scope);
881 self.scrollbar_generations
882 .borrow_mut()
883 .retain(|identity, _| identity.scope != scope);
884 }
885
886 pub(crate) fn mark_view_dependency(&self, dependency: &ScrollDependency) {
887 match dependency.kind {
888 ScrollDependencyKind::Metrics => {
889 self.metrics_view_dependencies
890 .borrow_mut()
891 .insert(dependency.identity.clone());
892 }
893 ScrollDependencyKind::Scrollbars => {
894 self.scrollbar_view_dependencies
895 .borrow_mut()
896 .insert(dependency.identity.clone());
897 }
898 }
899 }
900
901 pub(crate) fn dependency_generation(&self, dependency: &ScrollDependency) -> u64 {
902 let generations = match dependency.kind {
903 ScrollDependencyKind::Metrics => &self.metrics_generations,
904 ScrollDependencyKind::Scrollbars => &self.scrollbar_generations,
905 };
906 generations
907 .borrow()
908 .get(&dependency.identity)
909 .copied()
910 .unwrap_or(0)
911 }
912
913 pub(crate) fn view_generations(&self) -> ScrollGenerations {
914 ScrollGenerations {
915 metrics: self.metrics_generations.borrow().clone(),
916 scrollbars: self.scrollbar_generations.borrow().clone(),
917 }
918 }
919
920 pub(crate) fn view_dependencies_stale(&self, prev: &ScrollGenerations) -> bool {
923 self.metrics_view_dependencies
924 .borrow()
925 .iter()
926 .any(|identity| {
927 self.metrics_generations
928 .borrow()
929 .get(identity)
930 .copied()
931 .unwrap_or(0)
932 != prev.metrics.get(identity).copied().unwrap_or(0)
933 })
934 || self
935 .scrollbar_view_dependencies
936 .borrow()
937 .iter()
938 .any(|identity| {
939 self.scrollbar_generations
940 .borrow()
941 .get(identity)
942 .copied()
943 .unwrap_or(0)
944 != prev.scrollbars.get(identity).copied().unwrap_or(0)
945 })
946 }
947}
948
949fn node_scope(tree: &NodeTree, mut id: NodeId) -> ScopeId {
950 loop {
951 let node = tree.node(id);
952 if let NodeKind::Group(group) = &node.kind {
953 return group.scope;
954 }
955 let Some(parent) = node.parent.filter(|parent| tree.is_valid(*parent)) else {
956 return ScopeId(1);
957 };
958 id = parent;
959 }
960}
961
962fn advance_changed_generations<T: PartialEq>(
963 previous: &FxHashMap<ScrollIdentity, T>,
964 current: &FxHashMap<ScrollIdentity, T>,
965 generations: &RefCell<FxHashMap<ScrollIdentity, u64>>,
966) {
967 let identities: FxHashSet<_> = previous.keys().chain(current.keys()).cloned().collect();
968 let mut generations = generations.borrow_mut();
969 for identity in identities {
970 if previous.get(&identity) != current.get(&identity) {
971 let generation = generations.entry(identity).or_default();
972 *generation = generation.wrapping_add(1).max(1);
973 }
974 }
975 generations.retain(|identity, _| current.contains_key(identity));
976}
977
978pub struct Context<C: Component> {
980 pub state: C::State,
982
983 pub props: C::Properties,
985
986 viewport: Rect,
987 link: Link<C::Message>,
988 env: RuntimeEnv,
989 scope: ScopeId,
990}
991
992impl<C: Component> Context<C> {
993 pub(crate) fn new(
994 component: &C,
995 scope: ScopeId,
996 dispatcher: Dispatcher,
997 props: C::Properties,
998 env: RuntimeEnv,
999 viewport: Rect,
1000 ) -> Self {
1001 let state = component.create_state(&props);
1002 Self {
1003 state,
1004 props,
1005 viewport,
1006 link: Link::new(scope, dispatcher),
1007 env,
1008 scope,
1009 }
1010 }
1011
1012 pub fn link(&self) -> &Link<C::Message> {
1014 &self.link
1015 }
1016
1017 pub fn toast(&self) -> crate::overlay::ToastHandle {
1019 crate::overlay::ToastHandle::new(self.env.overlay_manager.clone())
1020 }
1021
1022 pub fn clipboard(&self) -> crate::clipboard::ClipboardHandle {
1024 crate::clipboard::ClipboardHandle::new(
1025 self.env.clipboard.clone(),
1026 self.env.clipboard_config.clone(),
1027 )
1028 }
1029
1030 pub fn command_registry(&self) -> CommandRegistry {
1032 self.env.command_registry.clone()
1033 }
1034
1035 pub fn command_chord_pending(&self) -> bool {
1042 self.env.command_chord_pending_since.get().is_some()
1043 }
1044
1045 pub fn command_chord_pending_since(&self) -> Option<Instant> {
1047 self.env.command_chord_pending_since.get()
1048 }
1049
1050 pub fn set_command_chord_reveal_delay(&self, delay: std::time::Duration) {
1055 self.env.command_chord_reveal_delay.set(delay);
1056 }
1057
1058 pub fn command_chord_revealed(&self) -> bool {
1067 self.env
1068 .command_chord_pending_since
1069 .get()
1070 .is_some_and(|since| {
1071 self.env.elapsed(since) >= self.env.command_chord_reveal_delay.get()
1072 })
1073 }
1074
1075 pub fn register_command(&self, entry: CommandEntry) {
1077 self.env
1078 .command_registry
1079 .register_for_scope(self.scope, entry);
1080 }
1081
1082 pub fn viewport(&self) -> Rect {
1084 self.env.note_memo_dependency(MemoDependency::Viewport);
1085 self.viewport
1086 }
1087
1088 pub fn theme(&self) -> Theme {
1090 self.env.note_memo_dependency(MemoDependency::Theme);
1091 self.env.active_theme.borrow().clone()
1092 }
1093
1094 pub fn theme_extension<T>(&self) -> Option<T>
1096 where
1097 T: ThemeExtension,
1098 {
1099 self.env.note_memo_dependency(MemoDependency::Theme);
1100 self.env.active_theme.borrow().extension_cloned::<T>()
1101 }
1102
1103 pub fn use_context<T>(&self) -> Option<T>
1105 where
1106 T: ContextValue,
1107 {
1108 self.env.note_memo_dependency(MemoDependency::Context {
1109 type_id: TypeId::of::<T>(),
1110 name: std::any::type_name::<T>(),
1111 });
1112 self.env
1113 .contexts
1114 .borrow()
1115 .get(&TypeId::of::<T>())
1116 .and_then(|value| value.as_ref().downcast_ref::<T>())
1117 .cloned()
1118 }
1119
1120 pub fn context<T>(&self) -> Option<T>
1122 where
1123 T: ContextValue,
1124 {
1125 self.use_context::<T>()
1126 }
1127
1128 pub fn is_inline(&self) -> bool {
1130 self.env.surface_mode.is_inline()
1131 }
1132
1133 pub fn surface_mode(&self) -> SurfaceMode {
1135 self.env.surface_mode
1136 }
1137
1138 pub fn effect_phase(&self) -> u64 {
1143 self.env.effect_phase.get()
1144 }
1145
1146 pub fn host_terminal_colors(&self) -> Option<HostTerminalColors> {
1153 self.env.host_terminal_colors()
1154 }
1155
1156 pub fn host_terminal_color_generation(&self) -> u64 {
1162 self.env.host_terminal_color_generation()
1163 }
1164
1165 pub fn request_host_terminal_color_refresh(&self) {
1175 self.env.request_host_terminal_color_refresh();
1176 }
1177
1178 pub fn last_mouse(&self) -> Option<(u16, u16)> {
1186 self.env.last_mouse.get()
1187 }
1188
1189 pub fn mouse_capture_enabled(&self) -> bool {
1191 self.env.note_memo_dependency(MemoDependency::MouseCapture);
1192 self.env.mouse_capture.get()
1193 }
1194
1195 pub fn set_mouse_capture(&self, enabled: bool) {
1197 if self.env.mouse_capture.get() != enabled {
1198 self.env.mouse_capture.set(enabled);
1199 self.env.mouse_capture_generation.set(
1200 self.env
1201 .mouse_capture_generation
1202 .get()
1203 .wrapping_add(1)
1204 .max(1),
1205 );
1206 }
1207 }
1208
1209 pub fn toggle_mouse_capture(&self) -> bool {
1211 let next = !self.env.mouse_capture.get();
1212 self.set_mouse_capture(next);
1213 next
1214 }
1215
1216 pub fn suspend_to_shell(&self) {
1231 crate::app::job_control::request_suspend();
1232 }
1233
1234 pub fn append_transcript_lines<I, L>(&mut self, lines: I)
1238 where
1239 I: IntoIterator<Item = L>,
1240 L: Into<RichText>,
1241 {
1242 if !matches!(self.env.surface_mode, SurfaceMode::InlineTranscript { .. }) {
1243 return;
1244 }
1245
1246 let lines: Vec<RichText> = lines.into_iter().map(Into::into).collect();
1247 if lines.is_empty() {
1248 return;
1249 }
1250
1251 self.env
1252 .transcript_history
1253 .borrow_mut()
1254 .push(TranscriptEntry::Lines(lines.clone()));
1255 self.env
1256 .pending_transcript_entries
1257 .borrow_mut()
1258 .push_back(TranscriptEntry::Lines(lines));
1259 }
1260
1261 pub fn append_transcript_element(&mut self, element: impl Into<Element>) {
1266 if !matches!(self.env.surface_mode, SurfaceMode::InlineTranscript { .. }) {
1267 return;
1268 }
1269
1270 let element = element.into();
1271 if element.contains_unexpanded_component() {
1272 crate::debug::internal_log!(
1273 "[tui-lipan] append_transcript_element ignored an element containing Component nodes"
1274 );
1275 return;
1276 }
1277
1278 self.env
1279 .transcript_history
1280 .borrow_mut()
1281 .push(TranscriptEntry::Element(Box::new(element.clone())));
1282 self.env
1283 .pending_transcript_entries
1284 .borrow_mut()
1285 .push_back(TranscriptEntry::Element(Box::new(element)));
1286 }
1287
1288 pub fn has_focus_within(&self) -> bool {
1291 self.env.note_memo_dependency(MemoDependency::Focus);
1292 self.env.focus.has_focus_within_scope(self.scope)
1293 }
1294
1295 pub fn has_focus_within_key(&self, key: impl Into<Key>) -> bool {
1298 let key = key.into();
1299 self.env.note_memo_dependency(MemoDependency::Focus);
1300 self.env.focus.has_focus_within_key(&key)
1301 }
1302
1303 pub fn text_area_scrollbars(&self, key: impl Into<Key>) -> ScrollbarVisibility {
1308 let dependency = ScrollDependency {
1309 identity: ScrollIdentity {
1310 scope: self.scope,
1311 key: key.into(),
1312 },
1313 kind: ScrollDependencyKind::Scrollbars,
1314 };
1315 self.env
1316 .note_memo_dependency(MemoDependency::Scroll(dependency.clone()));
1317 self.env.scroll.mark_view_dependency(&dependency);
1318 self.env
1319 .scroll
1320 .text_area_metrics(&dependency.identity)
1321 .map(|metrics| metrics.scrollbars)
1322 .or_else(|| self.env.scroll.get(&dependency.identity))
1323 .unwrap_or_default()
1324 }
1325
1326 pub fn text_area_metrics(
1328 &self,
1329 key: impl Into<Key>,
1330 ) -> Option<crate::widgets::TextAreaMetrics> {
1331 let dependency = ScrollDependency {
1332 identity: ScrollIdentity {
1333 scope: self.scope,
1334 key: key.into(),
1335 },
1336 kind: ScrollDependencyKind::Metrics,
1337 };
1338 self.env
1339 .note_memo_dependency(MemoDependency::Scroll(dependency.clone()));
1340 self.env.scroll.mark_view_dependency(&dependency);
1341 self.env.scroll.text_area_metrics(&dependency.identity)
1342 }
1343
1344 pub fn has_hover_within(&self) -> bool {
1347 self.env.note_memo_dependency(MemoDependency::Hover);
1348 self.env.hover.has_hover_within_scope(self.scope)
1349 }
1350
1351 pub fn has_hover_within_key(&self, key: impl Into<Key>) -> bool {
1354 let key = key.into();
1355 self.env.note_memo_dependency(MemoDependency::Hover);
1356 self.env.hover.has_hover_within_key(self.scope, &key)
1357 }
1358
1359 pub fn focused_node_id(&self) -> Option<NodeId> {
1361 self.env.note_memo_dependency(MemoDependency::Focus);
1362 self.env.focus.focused_node_id()
1363 }
1364
1365 pub fn hovered_node_id(&self) -> Option<NodeId> {
1367 self.env.note_memo_dependency(MemoDependency::Hover);
1368 self.env.hover.hovered_node_id(self.scope)
1369 }
1370
1371 pub fn transition<T>(
1396 &self,
1397 key: impl Into<Key>,
1398 target: T,
1399 config: crate::animation::TransitionConfig,
1400 ) -> T
1401 where
1402 T: crate::animation::Lerp + PartialEq + 'static,
1403 {
1404 let key = key.into();
1405 self.env.note_memo_dependency(MemoDependency::Transition);
1406 self.env.animations.transition(key, target, config)
1407 }
1408
1409 pub fn animated_color(
1436 &self,
1437 key: impl Into<Key>,
1438 target: crate::style::Color,
1439 config: crate::animation::TransitionConfig,
1440 ) -> crate::style::Paint {
1441 self.env
1444 .animations
1445 .animated_paint(key.into(), target, config)
1446 }
1447
1448 pub fn breakpoint(&self, medium: u16, large: u16) -> Breakpoint {
1455 let (medium, large) = if medium <= large {
1456 (medium, large)
1457 } else {
1458 (large, medium)
1459 };
1460
1461 self.env.note_memo_dependency(MemoDependency::Viewport);
1462 let w = self.viewport.w;
1463 if w < medium {
1464 Breakpoint::Small
1465 } else if w < large {
1466 Breakpoint::Medium
1467 } else {
1468 Breakpoint::Large
1469 }
1470 }
1471
1472 pub(crate) fn env(&self) -> &RuntimeEnv {
1473 &self.env
1474 }
1475
1476 pub(crate) fn set_viewport(&mut self, viewport: Rect) {
1477 self.viewport = viewport;
1478 }
1479
1480 pub(crate) fn set_active_theme(&mut self, theme: Theme) {
1481 let mut active_theme = self.env.active_theme.borrow_mut();
1482 if *active_theme != theme {
1483 *active_theme = theme;
1484 self.env.active_theme_generation.set(
1485 self.env
1486 .active_theme_generation
1487 .get()
1488 .wrapping_add(1)
1489 .max(1),
1490 );
1491 }
1492 }
1493
1494 pub(crate) fn set_contexts(
1495 &mut self,
1496 contexts: rustc_hash::FxHashMap<TypeId, std::sync::Arc<dyn std::any::Any>>,
1497 generations: rustc_hash::FxHashMap<TypeId, u64>,
1498 ) {
1499 *self.env.contexts.borrow_mut() = contexts;
1500 *self.env.context_generations.borrow_mut() = generations;
1501 }
1502
1503 pub(crate) fn memo_key(&self, component: &C) -> Option<u64> {
1504 component.memo_key(&self.props, self)
1505 }
1506
1507 pub(crate) fn begin_memo_dependency_capture(&self) {
1508 self.env.scroll.begin_view(self.scope);
1509 self.env.begin_memo_dependency_capture();
1510 }
1511
1512 pub(crate) fn finish_memo_dependency_capture(&self) -> MemoDependencySnapshot {
1513 self.env.finish_memo_dependency_capture(self.viewport)
1514 }
1515
1516 pub(crate) fn memo_dependencies_match(&self, snapshot: &MemoDependencySnapshot) -> bool {
1517 snapshot.matches(&self.env, self.viewport)
1518 }
1519
1520 #[cfg(feature = "devtools")]
1521 pub(crate) fn memo_dependency_mismatch(
1522 &self,
1523 snapshot: &MemoDependencySnapshot,
1524 ) -> Option<crate::core::nested::MemoDependencyKind> {
1525 snapshot.first_mismatch(&self.env, self.viewport)
1526 }
1527
1528 pub fn quit(&mut self) {
1530 self.env.quit.set(true);
1531 }
1532
1533 pub(crate) fn request_quit(&self) {
1538 self.env.quit.set(true);
1539 }
1540
1541 pub fn request_focus(&mut self, key: impl Into<Key>) {
1545 *self.env.focus_request.borrow_mut() = Some(FocusRequest::Key(key.into()));
1546 }
1547
1548 pub fn blur(&mut self) {
1552 *self.env.focus_request.borrow_mut() = Some(FocusRequest::Clear);
1553 }
1554
1555 pub fn focus_next(&mut self) {
1557 *self.env.focus_request.borrow_mut() = Some(FocusRequest::Next);
1558 }
1559
1560 pub fn focus_prev(&mut self) {
1562 *self.env.focus_request.borrow_mut() = Some(FocusRequest::Prev);
1563 }
1564
1565 pub fn request_full_repaint(&self) {
1570 self.env.full_repaint.set(true);
1571 }
1572
1573 pub fn flash_copy_feedback(&self, node_id: NodeId) {
1582 self.env.request_copy_feedback(node_id, None);
1583 }
1584
1585 pub fn flash_copy_feedback_range(&self, node_id: NodeId, range: GridSelection) {
1598 self.env.request_copy_feedback(node_id, Some(range));
1599 }
1600
1601 pub fn set_devtools_metrics<F, I>(&self, metrics: F)
1616 where
1617 F: FnOnce() -> I,
1618 I: IntoIterator<Item = crate::DevToolsMetric>,
1619 {
1620 #[cfg(feature = "devtools")]
1621 {
1622 self.env
1623 .devtools_metrics
1624 .replace(metrics().into_iter().collect());
1625 }
1626 #[cfg(not(feature = "devtools"))]
1627 {
1628 let _ = metrics;
1629 }
1630 }
1631
1632 #[cfg(feature = "devtools")]
1633 pub(crate) fn devtools_metrics(
1634 &self,
1635 ) -> std::rc::Rc<crate::core::runtime_env::DevToolsMetrics> {
1636 std::rc::Rc::clone(&self.env.devtools_metrics)
1637 }
1638
1639 pub fn devtools_visible(&self) -> bool {
1645 #[cfg(feature = "devtools")]
1646 {
1647 self.env.devtools_metrics.is_visible()
1648 }
1649 #[cfg(not(feature = "devtools"))]
1650 {
1651 false
1652 }
1653 }
1654
1655 pub fn show_devtools(&self) {
1657 *self.env.devtools_request.borrow_mut() = Some(DevToolsRequest::Show);
1658 }
1659
1660 pub fn hide_devtools(&self) {
1662 *self.env.devtools_request.borrow_mut() = Some(DevToolsRequest::Hide);
1663 }
1664
1665 pub fn toggle_devtools(&self) {
1667 *self.env.devtools_request.borrow_mut() = Some(DevToolsRequest::Toggle);
1668 }
1669
1670 pub(crate) fn take_focus_request(&self) -> Option<FocusRequest> {
1671 self.env.focus_request.borrow_mut().take()
1672 }
1673
1674 pub(crate) fn take_full_repaint_request(&self) -> bool {
1675 self.env.full_repaint.replace(false)
1676 }
1677
1678 pub(crate) fn take_copy_feedback_requests(&self) -> Vec<CopyFeedbackRequest> {
1679 self.env.take_copy_feedback_requests()
1680 }
1681
1682 pub(crate) fn take_devtools_request(&self) -> Option<DevToolsRequest> {
1683 self.env.devtools_request.borrow_mut().take()
1684 }
1685
1686 pub fn request_ui_snapshot_to(&self, path: impl AsRef<std::path::Path>) {
1695 let path = path.as_ref().to_path_buf();
1696 let format = crate::ui_snapshot::UiSnapshotFileFormat::from_path(&path);
1697 *self.env.ui_snapshot_request.borrow_mut() =
1698 Some(crate::ui_snapshot::UiSnapshotRequest::Write { path, format });
1699 self.request_full_repaint();
1700 }
1701
1702 pub fn request_ui_snapshot_to_slot(&self, slot: &crate::ui_snapshot::UiSnapshotSlot) {
1707 *self.env.ui_snapshot_request.borrow_mut() = Some(
1708 crate::ui_snapshot::UiSnapshotRequest::Deliver(slot.shared()),
1709 );
1710 self.request_full_repaint();
1711 }
1712
1713 pub(crate) fn take_ui_snapshot_request(&self) -> Option<crate::ui_snapshot::UiSnapshotRequest> {
1714 self.env.ui_snapshot_request.borrow_mut().take()
1715 }
1716
1717 pub(crate) fn should_quit(&self) -> bool {
1718 self.env.quit.get()
1719 }
1720}
1721
1722#[cfg(test)]
1723mod tests;