gpui_base/
global_state.rs1use std::rc::{Rc, Weak};
2
3use gpui::{App, Entity, Global, OwnedMenu};
4
5use crate::text::TextViewState;
6
7pub struct DeferredPopover(#[allow(dead_code)] Rc<()>);
13
14pub struct GlobalState {
16 app_menus: Vec<OwnedMenu>,
17 deferred_popovers: Vec<Weak<()>>,
18 suppress_text_selection: bool,
19 pub(crate) text_view_state_stack: Vec<Entity<TextViewState>>,
20 selection_document_order: u64,
21 last_touch: Option<std::time::Instant>,
24}
25
26impl Global for GlobalState {}
27
28impl GlobalState {
29 fn new() -> Self {
30 Self {
31 app_menus: Vec::new(),
32 deferred_popovers: Vec::new(),
33 suppress_text_selection: false,
34 text_view_state_stack: Vec::new(),
35 selection_document_order: 1,
36 last_touch: None,
37 }
38 }
39
40 pub fn note_touch(cx: &mut App) {
43 Self::init(cx);
44 Self::global_mut(cx).last_touch = Some(std::time::Instant::now());
45 }
46
47 pub fn is_touch_press(cx: &App) -> bool {
51 cx.try_global::<Self>()
52 .and_then(|state| state.last_touch)
53 .is_some_and(|at| at.elapsed() < std::time::Duration::from_secs(1))
54 }
55
56 #[doc(hidden)]
58 pub fn init(cx: &mut App) {
59 if !cx.has_global::<Self>() {
60 cx.set_global(Self::new());
61 }
62 }
63
64 pub fn suppress_text_selection(cx: &mut App) {
69 Self::global_mut(cx).suppress_text_selection = true;
70 }
71
72 #[doc(hidden)]
74 pub fn reset_text_selection_suppression(cx: &mut App) {
75 Self::global_mut(cx).suppress_text_selection = false;
76 }
77
78 #[doc(hidden)]
80 pub fn is_text_selection_suppressed(cx: &App) -> bool {
81 Self::global(cx).suppress_text_selection
82 }
83
84 pub fn global(cx: &App) -> &Self {
85 cx.global::<Self>()
86 }
87
88 pub fn global_mut(cx: &mut App) -> &mut Self {
89 cx.global_mut::<Self>()
90 }
91
92 pub(crate) fn text_view_state(&self) -> Option<&Entity<TextViewState>> {
93 self.text_view_state_stack.last()
94 }
95
96 #[doc(hidden)]
97 pub fn begin_selection_frame(&mut self) {
98 self.selection_document_order = 1;
99 }
100
101 pub(crate) fn next_selection_document_order(&mut self) -> u64 {
102 let order = self.selection_document_order;
103 self.selection_document_order = self.selection_document_order.wrapping_add(1);
104 order
105 }
106
107 pub fn app_menus(&self) -> &[OwnedMenu] {
109 &self.app_menus
110 }
111
112 pub fn set_app_menus(&mut self, menus: Vec<OwnedMenu>) {
114 self.app_menus = menus;
115 }
116
117 pub fn is_in_deferred_context(cx: &App) -> bool {
120 Self::global(cx)
121 .deferred_popovers
122 .iter()
123 .any(|popover| popover.strong_count() > 0)
124 }
125
126 pub fn register_deferred_popover(cx: &mut App) -> DeferredPopover {
138 let token = Rc::new(());
139 let state = Self::global_mut(cx);
140 state
141 .deferred_popovers
142 .retain(|popover| popover.strong_count() > 0);
143 state.deferred_popovers.push(Rc::downgrade(&token));
144 DeferredPopover(token)
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[gpui::test]
153 fn initialization_is_idempotent_and_suppression_can_be_reset(cx: &mut gpui::TestAppContext) {
154 cx.update(|cx| {
155 GlobalState::init(cx);
156 GlobalState::suppress_text_selection(cx);
157 GlobalState::init(cx);
158 assert!(GlobalState::is_text_selection_suppressed(cx));
159
160 GlobalState::reset_text_selection_suppression(cx);
161 assert!(!GlobalState::is_text_selection_suppressed(cx));
162
163 assert!(!GlobalState::is_in_deferred_context(cx));
164 let popover = GlobalState::register_deferred_popover(cx);
165 assert!(GlobalState::is_in_deferred_context(cx));
166 drop(popover);
167 assert!(!GlobalState::is_in_deferred_context(cx));
168 });
169 }
170
171 #[gpui::test]
175 fn a_dropped_registration_closes_the_deferred_context(cx: &mut gpui::TestAppContext) {
176 cx.update(|cx| {
177 GlobalState::init(cx);
178
179 let outer = GlobalState::register_deferred_popover(cx);
180 {
181 let _inner = GlobalState::register_deferred_popover(cx);
182 assert!(GlobalState::is_in_deferred_context(cx));
183 }
184 assert!(GlobalState::is_in_deferred_context(cx));
185
186 drop(outer);
187 assert!(!GlobalState::is_in_deferred_context(cx));
188
189 let popover = GlobalState::register_deferred_popover(cx);
191 assert_eq!(GlobalState::global(cx).deferred_popovers.len(), 1);
192 drop(popover);
193 assert!(!GlobalState::is_in_deferred_context(cx));
194 });
195 }
196}