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
14pub 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 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 pub fn show(&mut self) {
45 let _ = self.webview.set_visible(true);
46 self.visible = true;
47 }
48
49 pub fn hide(&mut self) {
51 _ = self.webview.focus_parent();
52 _ = self.webview.set_visible(false);
53 self.visible = false;
54 }
55
56 pub fn visible(&self) -> bool {
58 self.visible
59 }
60
61 pub fn bounds(&self) -> Bounds<Pixels> {
63 self.bounds
64 }
65
66 pub fn back(&mut self) -> anyhow::Result<()> {
68 Ok(self.webview.evaluate_script("history.back();")?)
69 }
70
71 pub fn load_url(&mut self, url: &str) {
73 self.webview.load_url(url).unwrap();
74 }
75
76 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
122pub struct WebViewElement {
124 parent: Entity<WebView>,
125 view: Rc<wry::WebView>,
126}
127
128impl WebViewElement {
129 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 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 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 let _ = webview.focus_parent();
224 }
225 });
226 });
227 }
228}