gpui_base/
global_state.rs1use std::rc::{Rc, Weak};
2#[cfg(not(target_family = "wasm"))]
3use std::time::Instant;
4#[cfg(target_family = "wasm")]
5use web_time::Instant;
6
7use gpui::{App, Entity, Global, OwnedMenu};
8
9use crate::text::TextViewState;
10
11pub struct DeferredPopover(#[allow(dead_code)] Rc<()>);
17
18pub struct GlobalState {
20 app_menus: Vec<OwnedMenu>,
21 deferred_popovers: Vec<Weak<()>>,
22 suppress_text_selection: bool,
23 pub(crate) text_view_state_stack: Vec<Entity<TextViewState>>,
24 selection_document_order: u64,
25 last_touch: Option<Instant>,
28}
29
30impl Global for GlobalState {}
31
32impl GlobalState {
33 fn new() -> Self {
34 Self {
35 app_menus: Vec::new(),
36 deferred_popovers: Vec::new(),
37 suppress_text_selection: false,
38 text_view_state_stack: Vec::new(),
39 selection_document_order: 1,
40 last_touch: None,
41 }
42 }
43
44 pub fn note_touch(cx: &mut App) {
47 Self::init(cx);
48 Self::global_mut(cx).last_touch = Some(Instant::now());
49 }
50
51 pub fn is_touch_press(cx: &App) -> bool {
55 cx.try_global::<Self>()
56 .and_then(|state| state.last_touch)
57 .is_some_and(|at| at.elapsed() < std::time::Duration::from_secs(1))
58 }
59
60 #[doc(hidden)]
62 pub fn init(cx: &mut App) {
63 if !cx.has_global::<Self>() {
64 cx.set_global(Self::new());
65 }
66 }
67
68 pub fn suppress_text_selection(cx: &mut App) {
73 Self::global_mut(cx).suppress_text_selection = true;
74 }
75
76 #[doc(hidden)]
78 pub fn reset_text_selection_suppression(cx: &mut App) {
79 Self::global_mut(cx).suppress_text_selection = false;
80 }
81
82 #[doc(hidden)]
84 pub fn is_text_selection_suppressed(cx: &App) -> bool {
85 Self::global(cx).suppress_text_selection
86 }
87
88 pub fn global(cx: &App) -> &Self {
89 cx.global::<Self>()
90 }
91
92 pub fn global_mut(cx: &mut App) -> &mut Self {
93 cx.global_mut::<Self>()
94 }
95
96 pub(crate) fn text_view_state(&self) -> Option<&Entity<TextViewState>> {
97 self.text_view_state_stack.last()
98 }
99
100 #[doc(hidden)]
101 pub fn begin_selection_frame(&mut self) {
102 self.selection_document_order = 1;
103 }
104
105 pub(crate) fn next_selection_document_order(&mut self) -> u64 {
106 let order = self.selection_document_order;
107 self.selection_document_order = self.selection_document_order.wrapping_add(1);
108 order
109 }
110
111 pub fn app_menus(&self) -> &[OwnedMenu] {
113 &self.app_menus
114 }
115
116 pub fn set_app_menus(&mut self, menus: Vec<OwnedMenu>) {
118 self.app_menus = menus;
119 }
120
121 pub fn is_in_deferred_context(cx: &App) -> bool {
124 Self::global(cx)
125 .deferred_popovers
126 .iter()
127 .any(|popover| popover.strong_count() > 0)
128 }
129
130 pub fn register_deferred_popover(cx: &mut App) -> DeferredPopover {
142 let token = Rc::new(());
143 let state = Self::global_mut(cx);
144 state
145 .deferred_popovers
146 .retain(|popover| popover.strong_count() > 0);
147 state.deferred_popovers.push(Rc::downgrade(&token));
148 DeferredPopover(token)
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[gpui::test]
157 fn initialization_is_idempotent_and_suppression_can_be_reset(cx: &mut gpui::TestAppContext) {
158 cx.update(|cx| {
159 GlobalState::init(cx);
160 GlobalState::suppress_text_selection(cx);
161 GlobalState::init(cx);
162 assert!(GlobalState::is_text_selection_suppressed(cx));
163
164 GlobalState::reset_text_selection_suppression(cx);
165 assert!(!GlobalState::is_text_selection_suppressed(cx));
166
167 assert!(!GlobalState::is_in_deferred_context(cx));
168 let popover = GlobalState::register_deferred_popover(cx);
169 assert!(GlobalState::is_in_deferred_context(cx));
170 drop(popover);
171 assert!(!GlobalState::is_in_deferred_context(cx));
172 });
173 }
174
175 #[gpui::test]
179 fn a_dropped_registration_closes_the_deferred_context(cx: &mut gpui::TestAppContext) {
180 cx.update(|cx| {
181 GlobalState::init(cx);
182
183 let outer = GlobalState::register_deferred_popover(cx);
184 {
185 let _inner = GlobalState::register_deferred_popover(cx);
186 assert!(GlobalState::is_in_deferred_context(cx));
187 }
188 assert!(GlobalState::is_in_deferred_context(cx));
189
190 drop(outer);
191 assert!(!GlobalState::is_in_deferred_context(cx));
192
193 let popover = GlobalState::register_deferred_popover(cx);
195 assert_eq!(GlobalState::global(cx).deferred_popovers.len(), 1);
196 drop(popover);
197 assert!(!GlobalState::is_in_deferred_context(cx));
198 });
199 }
200}