use std::{ops::Deref, rc::Rc}; use wry::{ dpi::{self, LogicalSize}, Rect, }; use gpui::{ canvas, div, App, Bounds, ContentMask, DismissEvent, Element, ElementId, Entity, EventEmitter, FocusHandle, Focusable, GlobalElementId, Hitbox, InteractiveElement, IntoElement, LayoutId, MouseDownEvent, ParentElement as _, Pixels, Render, Size, Style, Styled as _, Window, }; use crate::PixelsExt; pub struct WebView { focus_handle: FocusHandle, webview: Rc, visible: bool, bounds: Bounds, } impl Drop for WebView { fn drop(&mut self) { self.hide(); } } impl WebView { pub fn new(webview: wry::WebView, _: &mut Window, cx: &mut App) -> Self { let _ = webview.set_bounds(Rect::default()); Self { focus_handle: cx.focus_handle(), visible: true, bounds: Bounds::default(), webview: Rc::new(webview), } } pub fn show(&mut self) { let _ = self.webview.set_visible(true); self.visible = true; } pub fn hide(&mut self) { _ = self.webview.focus_parent(); _ = self.webview.set_visible(false); self.visible = false; } pub fn visible(&self) -> bool { self.visible } pub fn bounds(&self) -> Bounds { self.bounds } /// Go back in the webview history. pub fn back(&mut self) -> anyhow::Result<()> { Ok(self.webview.evaluate_script("history.back();")?) } pub fn load_url(&mut self, url: &str) { self.webview.load_url(url).unwrap(); } } impl Deref for WebView { type Target = wry::WebView; fn deref(&self) -> &Self::Target { &self.webview } } impl Focusable for WebView { fn focus_handle(&self, _cx: &gpui::App) -> FocusHandle { self.focus_handle.clone() } } impl EventEmitter for WebView {} impl Render for WebView { fn render( &mut self, window: &mut gpui::Window, cx: &mut gpui::Context, ) -> impl IntoElement { let view = cx.entity().clone(); div() .track_focus(&self.focus_handle) .size_full() .child({ let view = cx.entity().clone(); canvas( move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds), |_, _, _, _| {}, ) .absolute() .size_full() }) .child(WebViewElement::new(self.webview.clone(), view, window, cx)) } } /// A webview element can display a wry webview. pub struct WebViewElement { parent: Entity, view: Rc, } impl WebViewElement { /// Create a new webview element from a wry WebView. pub fn new( view: Rc, parent: Entity, _window: &mut Window, _cx: &mut App, ) -> Self { Self { view, parent } } } impl IntoElement for WebViewElement { type Element = WebViewElement; fn into_element(self) -> Self::Element { self } } impl Element for WebViewElement { type RequestLayoutState = (); type PrepaintState = Option; fn id(&self) -> Option { None } fn source_location(&self) -> Option<&'static std::panic::Location<'static>> { None } fn request_layout( &mut self, _: Option<&GlobalElementId>, _: Option<&gpui::InspectorElementId>, window: &mut Window, cx: &mut App, ) -> (LayoutId, Self::RequestLayoutState) { let mut style = Style::default(); style.flex_grow = 0.0; style.flex_shrink = 1.; style.size = Size::full(); // If the parent view is no longer visible, we don't need to layout the webview let id = window.request_layout(style, [], cx); (id, ()) } fn prepaint( &mut self, _: Option<&GlobalElementId>, _: Option<&gpui::InspectorElementId>, bounds: Bounds, _: &mut Self::RequestLayoutState, window: &mut Window, cx: &mut App, ) -> Self::PrepaintState { if !self.parent.read(cx).visible() { return None; } self.view .set_bounds(Rect { size: dpi::Size::Logical(LogicalSize { width: (bounds.size.width.as_f32()).into(), height: (bounds.size.height.as_f32()).into(), }), position: dpi::Position::Logical(dpi::LogicalPosition::new( bounds.origin.x.into(), bounds.origin.y.into(), )), }) .unwrap(); // Create a hitbox to handle mouse event Some(window.insert_hitbox(bounds, gpui::HitboxBehavior::Normal)) } fn paint( &mut self, _: Option<&GlobalElementId>, _: Option<&gpui::InspectorElementId>, bounds: Bounds, _: &mut Self::RequestLayoutState, hitbox: &mut Self::PrepaintState, window: &mut Window, _: &mut App, ) { let bounds = hitbox.clone().map(|h| h.bounds).unwrap_or(bounds); window.with_content_mask(Some(ContentMask { bounds }), |window| { let webview = self.view.clone(); window.on_mouse_event(move |event: &MouseDownEvent, _, _, _| { if !bounds.contains(&event.position) { // Click white space to blur the input focus let _ = webview.focus_parent(); } }); }); } }