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