1#![allow(clippy::type_complexity)]
44#![warn(missing_docs)]
45#![cfg_attr(docsrs, feature(doc_cfg))]
46
47mod error;
48mod ffi;
49
50#[cfg(gpui_backend)]
51mod events;
52#[cfg(gpui_backend)]
53mod platform;
54#[cfg(gpui_backend)]
55mod runtime;
56#[cfg(all(gpui_backend, not(gpui_mobile)))]
57mod webview;
58
59#[cfg(gpui_android)]
60mod android;
61#[cfg(gpui_ios)]
62mod ios;
63#[cfg(all(test, not(gpui_ios)))]
65#[path = "ios/keys.rs"]
66#[allow(dead_code)]
67mod ios_keys;
68#[cfg(all(test, not(gpui_ios)))]
69#[path = "ios/text.rs"]
70#[allow(dead_code)]
71mod ios_text;
72
73#[cfg(gpui_android)]
75use android as mobile;
76#[cfg(gpui_ios)]
77use ios as mobile;
78
79pub use error::GpuiError;
80pub use gpui;
81#[cfg(all(gpui_backend, not(gpui_mobile)))]
82#[cfg_attr(docsrs, doc(cfg(not(any(target_os = "android", target_os = "ios")))))]
83pub use webview::{WebviewFrame, focus_webview, webview_frame};
84
85use tauri::Wry;
86
87#[derive(Default)]
89pub struct GpuiConfig {
90 font_fallback: Option<String>,
91 on_launch: Option<Box<dyn FnOnce(&mut gpui::App)>>,
92 configure: Option<Box<dyn FnOnce(gpui::Application) -> gpui::Application>>,
93}
94
95impl GpuiConfig {
96 pub fn new() -> Self {
98 Self::default()
99 }
100
101 pub fn font_fallback(mut self, family: impl Into<String>) -> Self {
103 self.font_fallback = Some(family.into());
104 self
105 }
106
107 pub fn on_launch(mut self, f: impl FnOnce(&mut gpui::App) + 'static) -> Self {
110 self.on_launch = Some(Box::new(f));
111 self
112 }
113
114 pub fn assets(mut self, assets: impl gpui::AssetSource) -> Self {
116 self.configure = Some(Box::new(move |app| app.with_assets(assets)));
117 self
118 }
119}
120
121#[derive(Clone, Debug)]
123#[non_exhaustive]
124pub struct GpuiOptions {
125 pub focus: bool,
127 pub transparent: bool,
130}
131
132impl Default for GpuiOptions {
133 fn default() -> Self {
134 Self {
135 focus: true,
136 transparent: false,
137 }
138 }
139}
140
141impl GpuiOptions {
142 pub fn focus(mut self, focus: bool) -> Self {
144 self.focus = focus;
145 self
146 }
147
148 pub fn transparent(mut self, transparent: bool) -> Self {
152 self.transparent = transparent;
153 self
154 }
155}
156
157pub fn init(app: &mut tauri::App<Wry>) -> Result<(), GpuiError> {
163 init_with(app, GpuiConfig::default())
164}
165
166pub fn init_with(app: &mut tauri::App<Wry>, config: GpuiConfig) -> Result<(), GpuiError> {
168 #[cfg(gpui_backend)]
169 {
170 runtime::initialize(app.handle().clone(), config)?;
171 app.wry_plugin(hook::GpuiHookBuilder);
172 #[cfg(gpui_android)]
173 app.handle().plugin(android_plugin())?;
174 Ok(())
175 }
176 #[cfg(not(gpui_backend))]
177 {
178 let _ = (app, config);
179 Err(GpuiError::UnsupportedOperation {
180 operation: "tauri-plugin-gpui on mobile without the `mobile` feature",
181 })
182 }
183}
184
185#[cfg(gpui_android)]
188fn android_plugin() -> tauri::plugin::TauriPlugin<Wry> {
189 tauri::plugin::Builder::new("gpui")
190 .setup(|app, api| {
191 let (tx, rx) = std::sync::mpsc::channel();
192 let _ = app;
193 tauri_runtime_wry::wry::prelude::dispatch(move |env, activity, _webview| {
194 tx.send(android::register_natives(env, activity)).ok();
195 });
196 rx.recv()
197 .map_err(|e| e.to_string())?
198 .map_err(|e| format!("registering GpuiView natives: {e}"))?;
199 api.register_android_plugin("app.tauri.gpui", "GpuiPlugin")?;
200 Ok(())
201 })
202 .build()
203}
204
205pub fn with_app<R>(f: impl FnOnce(&mut gpui::App) -> R) -> Result<R, GpuiError> {
210 #[cfg(gpui_backend)]
211 {
212 runtime::with(|runtime| runtime.update(f))
213 }
214 #[cfg(not(gpui_backend))]
215 {
216 let _ = f;
217 Err(GpuiError::NotInitialized)
218 }
219}
220
221thread_local! {
222 static BACK_HANDLER: std::cell::RefCell<Option<std::rc::Rc<dyn Fn(&mut gpui::App)>>> =
223 const { std::cell::RefCell::new(None) };
224}
225
226pub fn on_back(handler: impl Fn(&mut gpui::App) + 'static) {
232 BACK_HANDLER.with(|slot| *slot.borrow_mut() = Some(std::rc::Rc::new(handler)));
233}
234
235pub fn set_back_enabled(enabled: bool) {
238 #[cfg(gpui_android)]
239 android::set_back_enabled(enabled);
240 #[cfg(not(gpui_android))]
241 let _ = enabled;
242}
243
244#[cfg_attr(not(gpui_android), allow(dead_code))]
245pub(crate) fn back_handler() -> Option<std::rc::Rc<dyn Fn(&mut gpui::App)>> {
246 BACK_HANDLER.with(|slot| slot.borrow().clone())
247}
248
249pub trait GpuiWindowExt {
251 fn attach_gpui<F, V>(&self, root: F) -> Result<(), GpuiError>
261 where
262 F: FnOnce(&mut gpui::App) -> gpui::Entity<V> + 'static,
263 V: gpui::Render + 'static,
264 {
265 self.attach_gpui_with(GpuiOptions::default(), root)
266 }
267
268 fn attach_gpui_with<F, V>(&self, options: GpuiOptions, root: F) -> Result<(), GpuiError>
270 where
271 F: FnOnce(&mut gpui::App) -> gpui::Entity<V> + 'static,
272 V: gpui::Render + 'static,
273 {
274 self.attach_gpui_view(options, move |_, cx| root(cx))
275 }
276
277 fn attach_gpui_view<F, V>(&self, options: GpuiOptions, root: F) -> Result<(), GpuiError>
282 where
283 F: FnOnce(&mut gpui::Window, &mut gpui::App) -> gpui::Entity<V> + 'static,
284 V: gpui::Render + 'static;
285
286 fn is_gpui_attached(&self) -> bool;
288}
289
290impl GpuiWindowExt for tauri::Window<Wry> {
291 fn attach_gpui_view<F, V>(&self, options: GpuiOptions, root: F) -> Result<(), GpuiError>
292 where
293 F: FnOnce(&mut gpui::Window, &mut gpui::App) -> gpui::Entity<V> + 'static,
294 V: gpui::Render + 'static,
295 {
296 #[cfg(gpui_backend)]
297 {
298 runtime::with(|runtime| {
299 runtime.attach(
300 self,
301 options,
302 Box::new(move |cx, window_options| {
303 cx.open_window(window_options, root).map(Into::into)
304 }),
305 )
306 })
307 }
308 #[cfg(not(gpui_backend))]
309 {
310 let _ = (options, root);
311 Err(GpuiError::UnsupportedOperation {
312 operation: "attach_gpui on mobile",
313 })
314 }
315 }
316
317 fn is_gpui_attached(&self) -> bool {
318 #[cfg(gpui_backend)]
319 {
320 runtime::with(|runtime| Ok(runtime.is_attached(self.label()))).unwrap_or(false)
321 }
322 #[cfg(not(gpui_backend))]
323 {
324 false
325 }
326 }
327}
328
329#[cfg(gpui_backend)]
330mod hook {
331 use tauri::EventLoopMessage;
332 use tauri_runtime_wry::{
333 Context, EventLoopIterationContext, Message, Plugin, PluginBuilder, WebContextStore,
334 tao::{
335 event::Event,
336 event_loop::{ControlFlow, EventLoopProxy, EventLoopWindowTarget},
337 },
338 };
339
340 pub(crate) struct GpuiHookBuilder;
343 pub(crate) struct GpuiHook;
344
345 impl PluginBuilder<EventLoopMessage> for GpuiHookBuilder {
346 type Plugin = GpuiHook;
347
348 fn build(self, _context: Context<EventLoopMessage>) -> Self::Plugin {
349 GpuiHook
350 }
351 }
352
353 impl Plugin<EventLoopMessage> for GpuiHook {
354 fn on_event(
355 &mut self,
356 event: &Event<Message<EventLoopMessage>>,
357 _event_loop: &EventLoopWindowTarget<Message<EventLoopMessage>>,
358 proxy: &EventLoopProxy<Message<EventLoopMessage>>,
359 _control_flow: &mut ControlFlow,
360 context: EventLoopIterationContext<'_, EventLoopMessage>,
361 _web_context: &WebContextStore,
362 ) -> bool {
363 crate::runtime::with(|runtime| Ok(runtime.handle_event(event, proxy, &context)))
366 .unwrap_or(false)
367 }
368 }
369}