gpui_base/
global_state.rs1#[cfg(not(target_family = "wasm"))]
2use std::time::Instant;
3use std::{
4 cell::{Cell, RefCell},
5 rc::{Rc, Weak},
6};
7#[cfg(target_family = "wasm")]
8use web_time::Instant;
9
10use gpui::{App, Entity, Global, OwnedMenu};
11
12use crate::text::TextViewState;
13
14pub struct DeferredPopover(#[allow(dead_code)] Rc<()>);
20
21pub struct GlobalState {
23 app_menus: Vec<OwnedMenu>,
24 deferred_popovers: Vec<Weak<()>>,
25 suppress_text_selection: bool,
26 text_view_state_stack: RefCell<Vec<Entity<TextViewState>>>,
30 selection_document_order: Cell<u64>,
33 last_touch: Option<Instant>,
36}
37
38impl Global for GlobalState {}
39
40impl GlobalState {
41 fn new() -> Self {
42 Self {
43 app_menus: Vec::new(),
44 deferred_popovers: Vec::new(),
45 suppress_text_selection: false,
46 text_view_state_stack: RefCell::new(Vec::new()),
47 selection_document_order: Cell::new(1),
48 last_touch: None,
49 }
50 }
51
52 pub fn note_touch(cx: &mut App) {
55 Self::init(cx);
56 Self::global_mut(cx).last_touch = Some(Instant::now());
57 }
58
59 pub fn is_touch_press(cx: &App) -> bool {
63 cx.try_global::<Self>()
64 .and_then(|state| state.last_touch)
65 .is_some_and(|at| at.elapsed() < std::time::Duration::from_secs(1))
66 }
67
68 #[doc(hidden)]
70 pub fn init(cx: &mut App) {
71 if !cx.has_global::<Self>() {
72 cx.set_global(Self::new());
73 }
74 }
75
76 pub fn suppress_text_selection(cx: &mut App) {
81 Self::global_mut(cx).suppress_text_selection = true;
82 }
83
84 #[doc(hidden)]
86 pub fn reset_text_selection_suppression(cx: &mut App) {
87 Self::global_mut(cx).suppress_text_selection = false;
88 }
89
90 #[doc(hidden)]
92 pub fn is_text_selection_suppressed(cx: &App) -> bool {
93 Self::global(cx).suppress_text_selection
94 }
95
96 pub fn global(cx: &App) -> &Self {
97 cx.global::<Self>()
98 }
99
100 pub fn global_mut(cx: &mut App) -> &mut Self {
101 cx.global_mut::<Self>()
102 }
103
104 pub(crate) fn text_view_state(&self) -> Option<Entity<TextViewState>> {
105 self.text_view_state_stack.borrow().last().cloned()
106 }
107
108 pub(crate) fn push_text_view_state(&self, state: Entity<TextViewState>) {
109 self.text_view_state_stack.borrow_mut().push(state);
110 }
111
112 pub(crate) fn pop_text_view_state(&self) {
113 self.text_view_state_stack.borrow_mut().pop();
114 }
115
116 #[doc(hidden)]
117 pub fn begin_selection_frame(&self) {
118 self.selection_document_order.set(1);
119 }
120
121 pub(crate) fn next_selection_document_order(&self) -> u64 {
122 let order = self.selection_document_order.get();
123 self.selection_document_order.set(order.wrapping_add(1));
124 order
125 }
126
127 pub fn app_menus(&self) -> &[OwnedMenu] {
129 &self.app_menus
130 }
131
132 pub fn set_app_menus(&mut self, menus: Vec<OwnedMenu>) {
134 self.app_menus = menus;
135 }
136
137 pub fn is_in_deferred_context(cx: &App) -> bool {
140 Self::global(cx)
141 .deferred_popovers
142 .iter()
143 .any(|popover| popover.strong_count() > 0)
144 }
145
146 pub fn register_deferred_popover(cx: &mut App) -> DeferredPopover {
158 let token = Rc::new(());
159 let state = Self::global_mut(cx);
160 state
161 .deferred_popovers
162 .retain(|popover| popover.strong_count() > 0);
163 state.deferred_popovers.push(Rc::downgrade(&token));
164 DeferredPopover(token)
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[gpui::test]
173 fn initialization_is_idempotent_and_suppression_can_be_reset(cx: &mut gpui::TestAppContext) {
174 cx.update(|cx| {
175 GlobalState::init(cx);
176 GlobalState::suppress_text_selection(cx);
177 GlobalState::init(cx);
178 assert!(GlobalState::is_text_selection_suppressed(cx));
179
180 GlobalState::reset_text_selection_suppression(cx);
181 assert!(!GlobalState::is_text_selection_suppressed(cx));
182
183 assert!(!GlobalState::is_in_deferred_context(cx));
184 let popover = GlobalState::register_deferred_popover(cx);
185 assert!(GlobalState::is_in_deferred_context(cx));
186 drop(popover);
187 assert!(!GlobalState::is_in_deferred_context(cx));
188 });
189 }
190
191 #[gpui::test]
195 fn a_dropped_registration_closes_the_deferred_context(cx: &mut gpui::TestAppContext) {
196 cx.update(|cx| {
197 GlobalState::init(cx);
198
199 let outer = GlobalState::register_deferred_popover(cx);
200 {
201 let _inner = GlobalState::register_deferred_popover(cx);
202 assert!(GlobalState::is_in_deferred_context(cx));
203 }
204 assert!(GlobalState::is_in_deferred_context(cx));
205
206 drop(outer);
207 assert!(!GlobalState::is_in_deferred_context(cx));
208
209 let popover = GlobalState::register_deferred_popover(cx);
211 assert_eq!(GlobalState::global(cx).deferred_popovers.len(), 1);
212 drop(popover);
213 assert!(!GlobalState::is_in_deferred_context(cx));
214 });
215 }
216}