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#[derive(Clone)]
20pub struct WebViewHandle(Rc<wry::WebView>);
21
22impl WebViewHandle {
23 pub fn raw(&self) -> &wry::WebView {
25 &self.0
26 }
27}
28
29pub 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 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 pub fn show(&mut self) {
60 let _ = self.webview.set_visible(true);
61 self.visible = true;
62 }
63
64 pub fn hide(&mut self) {
66 _ = self.webview.focus_parent();
67 _ = self.webview.set_visible(false);
68 self.visible = false;
69 }
70
71 pub fn visible(&self) -> bool {
73 self.visible
74 }
75
76 pub fn bounds(&self) -> Bounds<Pixels> {
78 self.bounds
79 }
80
81 pub fn back(&mut self) -> anyhow::Result<()> {
83 Ok(self.webview.evaluate_script("history.back();")?)
84 }
85
86 pub fn load_url(&mut self, url: &str) {
88 let _ = self.webview.load_url(url);
89 }
90
91 pub fn raw(&self) -> &wry::WebView {
93 &self.webview
94 }
95
96 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
142pub struct WebViewElement {
144 parent: Entity<WebView>,
145 view: Rc<wry::WebView>,
146}
147
148impl WebViewElement {
149 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 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 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 let _ = webview.focus_parent();
243 }
244 });
245 });
246 }
247}