Skip to main content

gpui_wry/
lib.rs

1use std::{ops::Deref, rc::Rc};
2
3use wry::{
4    Rect,
5    dpi::{self, LogicalSize},
6};
7
8use gpui::{
9    App, Bounds, ContentMask, DismissEvent, Element, ElementId, Entity, EventEmitter, FocusHandle,
10    Focusable, GlobalElementId, Hitbox, InteractiveElement, IntoElement, LayoutId, MouseDownEvent,
11    ParentElement as _, Pixels, Render, Size, Style, Styled as _, Window, canvas, div,
12};
13
14/// An owned, UI-thread-local handle to the raw wry webview.
15///
16/// Cloning this handle prolongs the native webview's lifetime. Dropping the owning [`WebView`]
17/// entity hides the child view, but final native destruction waits until all handle and frame
18/// clones are dropped. All handles must be dropped before the parent window is destroyed.
19#[derive(Clone)]
20pub struct WebViewHandle(Rc<wry::WebView>);
21
22impl WebViewHandle {
23    /// Get the raw wry webview.
24    pub fn raw(&self) -> &wry::WebView {
25        &self.0
26    }
27}
28
29/// A webview based on wry WebView.
30///
31/// [experimental]
32pub struct WebView {
33    focus_handle: FocusHandle,
34    webview: Rc<wry::WebView>,
35    visible: bool,
36    bounds: Bounds<Pixels>,
37}
38
39impl Drop for WebView {
40    fn drop(&mut self) {
41        self.hide();
42    }
43}
44
45impl WebView {
46    /// Create a new WebView from a wry WebView.
47    pub fn new(webview: wry::WebView, _: &mut Window, cx: &mut App) -> Self {
48        let _ = webview.set_bounds(Rect::default());
49
50        Self {
51            focus_handle: cx.focus_handle(),
52            visible: true,
53            bounds: Bounds::default(),
54            webview: Rc::new(webview),
55        }
56    }
57
58    /// Show the webview.
59    pub fn show(&mut self) {
60        let _ = self.webview.set_visible(true);
61        self.visible = true;
62    }
63
64    /// Hide the webview.
65    pub fn hide(&mut self) {
66        _ = self.webview.focus_parent();
67        _ = self.webview.set_visible(false);
68        self.visible = false;
69    }
70
71    /// Get whether the webview is visible.
72    pub fn visible(&self) -> bool {
73        self.visible
74    }
75
76    /// Get the current bounds of the webview.
77    pub fn bounds(&self) -> Bounds<Pixels> {
78        self.bounds
79    }
80
81    /// Go back in the webview history.
82    pub fn back(&mut self) -> anyhow::Result<()> {
83        Ok(self.webview.evaluate_script("history.back();")?)
84    }
85
86    /// Load a URL in the webview.
87    pub fn load_url(&mut self, url: &str) {
88        let _ = self.webview.load_url(url);
89    }
90
91    /// Get the raw wry webview.
92    pub fn raw(&self) -> &wry::WebView {
93        &self.webview
94    }
95
96    /// Get an owned, UI-thread-local handle to the raw wry webview.
97    pub fn handle(&self) -> WebViewHandle {
98        WebViewHandle(self.webview.clone())
99    }
100}
101
102impl Deref for WebView {
103    type Target = wry::WebView;
104
105    fn deref(&self) -> &Self::Target {
106        &self.webview
107    }
108}
109
110impl Focusable for WebView {
111    fn focus_handle(&self, _cx: &gpui::App) -> FocusHandle {
112        self.focus_handle.clone()
113    }
114}
115
116impl EventEmitter<DismissEvent> for WebView {}
117
118impl Render for WebView {
119    fn render(
120        &mut self,
121        window: &mut gpui::Window,
122        cx: &mut gpui::Context<Self>,
123    ) -> impl IntoElement {
124        let view = cx.entity().clone();
125
126        div()
127            .track_focus(&self.focus_handle)
128            .size_full()
129            .child({
130                let view = cx.entity().clone();
131                canvas(
132                    move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds),
133                    |_, _, _, _| {},
134                )
135                .absolute()
136                .size_full()
137            })
138            .child(WebViewElement::new(self.webview.clone(), view, window, cx))
139    }
140}
141
142/// A webview element can display a wry webview.
143pub struct WebViewElement {
144    parent: Entity<WebView>,
145    view: Rc<wry::WebView>,
146}
147
148impl WebViewElement {
149    /// Create a new webview element from a wry WebView.
150    pub fn new(
151        view: Rc<wry::WebView>,
152        parent: Entity<WebView>,
153        _window: &mut Window,
154        _cx: &mut App,
155    ) -> Self {
156        Self { view, parent }
157    }
158}
159
160impl IntoElement for WebViewElement {
161    type Element = WebViewElement;
162
163    fn into_element(self) -> Self::Element {
164        self
165    }
166}
167
168impl Element for WebViewElement {
169    type RequestLayoutState = ();
170    type PrepaintState = Option<Hitbox>;
171
172    fn id(&self) -> Option<ElementId> {
173        None
174    }
175
176    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
177        None
178    }
179
180    fn request_layout(
181        &mut self,
182        _: Option<&GlobalElementId>,
183        _: Option<&gpui::InspectorElementId>,
184        window: &mut Window,
185        cx: &mut App,
186    ) -> (LayoutId, Self::RequestLayoutState) {
187        let style = Style {
188            size: Size::full(),
189            flex_shrink: 1.,
190            ..Default::default()
191        };
192
193        // If the parent view is no longer visible, we don't need to layout the webview
194        let id = window.request_layout(style, [], cx);
195        (id, ())
196    }
197
198    fn prepaint(
199        &mut self,
200        _: Option<&GlobalElementId>,
201        _: Option<&gpui::InspectorElementId>,
202        bounds: Bounds<Pixels>,
203        _: &mut Self::RequestLayoutState,
204        window: &mut Window,
205        cx: &mut App,
206    ) -> Self::PrepaintState {
207        if !self.parent.read(cx).visible() {
208            return None;
209        }
210
211        let _ = self.view.set_bounds(Rect {
212            size: dpi::Size::Logical(LogicalSize {
213                width: bounds.size.width.into(),
214                height: bounds.size.height.into(),
215            }),
216            position: dpi::Position::Logical(dpi::LogicalPosition::new(
217                bounds.origin.x.into(),
218                bounds.origin.y.into(),
219            )),
220        });
221
222        // Create a hitbox to handle mouse event
223        Some(window.insert_hitbox(bounds, gpui::HitboxBehavior::Normal))
224    }
225
226    fn paint(
227        &mut self,
228        _: Option<&GlobalElementId>,
229        _: Option<&gpui::InspectorElementId>,
230        bounds: Bounds<Pixels>,
231        _: &mut Self::RequestLayoutState,
232        hitbox: &mut Self::PrepaintState,
233        window: &mut Window,
234        _: &mut App,
235    ) {
236        let bounds = hitbox.clone().map(|h| h.bounds).unwrap_or(bounds);
237        window.with_content_mask(Some(ContentMask { bounds }), |window| {
238            let webview = self.view.clone();
239            window.on_mouse_event(move |event: &MouseDownEvent, _, _, _| {
240                if !bounds.contains(&event.position) {
241                    // Click white space to blur the input focus
242                    let _ = webview.focus_parent();
243                }
244            });
245        });
246    }
247}