Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gpui: Add WebView support #6

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,014 changes: 961 additions & 53 deletions Cargo.lock

Large diffs are not rendered by default.

12 changes: 10 additions & 2 deletions crates/gpui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ parking = "2.0.0"
parking_lot.workspace = true
postage.workspace = true
profiling.workspace = true
rand = { optional = true, workspace = true }
raw-window-handle = "0.6"
rand = { optional = true, workspace = true}
raw-window-handle = { version = "0.6", features = ["std"] }
refineable.workspace = true
resvg = { version = "0.44.0", default-features = false }
usvg = { version = "0.44.0", default-features = false }
Expand Down Expand Up @@ -117,6 +117,10 @@ rand.workspace = true
util = { workspace = true, features = ["test-support"] }
http_client = { workspace = true, features = ["test-support"] }
unicode-segmentation.workspace = true
wry = "0.41.0"

[target.'cfg(target_os = "linux")'.dev-dependencies]
gtk = "0.18.0"

[build-dependencies]
embed-resource = "3.0"
Expand Down Expand Up @@ -226,6 +230,10 @@ path = "examples/set_menus.rs"
name = "window_shadow"
path = "examples/window_shadow.rs"

[[example]]
name = 'webview'
path = "examples/webview.rs"

[[example]]
name = "input"
path = "examples/input.rs"
Expand Down
180 changes: 180 additions & 0 deletions crates/gpui/examples/webview.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
use std::rc::Rc;

use gpui::*;
use wry::{
dpi::{self, LogicalSize},
Rect,
};

struct WebViewWindow {
webview: View<WebView>,
}

impl WebViewWindow {
fn new(cx: &mut WindowContext) -> Self {
Self {
webview: cx.new_view(|cx| WebView::new(cx)),
}
}
}

impl Render for WebViewWindow {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
div().flex().bg(rgb(0xF0F0F0)).size_full().p_10().child(
div()
.flex()
.flex_col()
.size_full()
.justify_center()
.items_center()
.gap_4()
.child("Wry WebView Demo")
.child(self.webview.clone()),
)
}
}

fn main() {
#[cfg(target_os = "linux")]
{
gtk::init().unwrap();
}

App::new().run(|cx: &mut AppContext| {
let bounds = Bounds::centered(None, size(px(1200.0), px(800.0)), cx);
let window = cx
.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
kind: WindowKind::Normal,
..Default::default()
},
|cx| cx.new_view(|cx| WebViewWindow::new(cx)),
)
.unwrap();

cx.spawn(|mut cx| async move {
window
.update(&mut cx, |_, cx| {
cx.activate_window();
cx.set_window_title("WebView Example");
cx.on_release(|_, _, _cx| {
// exit app
std::process::exit(0);
})
.detach();
})
.unwrap();
})
.detach();
});
}

/// A webview element that can display a URL or HTML content.
pub struct WebView {
view: Rc<wry::WebView>,
}

impl WebView {
/// Create a new webview element.
pub fn new(cx: &mut WindowContext) -> Self {
let view = Rc::new(
wry::WebViewBuilder::new_as_child(&cx.raw_window_handle())
.with_url("https://zed.dev")
.build()
.expect("Failed to create webview."),
);

Self { view }
}
}

impl Render for WebView {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
div()
.id("WebView")
.block()
.overflow_y_scroll()
.size_full()
.justify_center()
.items_center()
.shadow_lg()
.border_1()
.rounded_md()
.bg(gpui::white())
.border_color(rgb(0xD0D0D0))
.child(WebViewElement {
view: self.view.clone(),
})
}
}

struct WebViewElement {
view: Rc<wry::WebView>,
}
impl IntoElement for WebViewElement {
type Element = WebViewElement;

fn into_element(self) -> Self::Element {
self
}
}

impl Element for WebViewElement {
type RequestLayoutState = ();
type PrepaintState = ();

fn id(&self) -> Option<ElementId> {
None
}

fn request_layout(
&mut self,
_: Option<&GlobalElementId>,
cx: &mut WindowContext,
) -> (LayoutId, Self::RequestLayoutState) {
let mut style = Style::default();
style.flex_grow = 1.0;
style.size = Size::full();
let id = cx.request_layout(style, []);
(id, ())
}

fn prepaint(
&mut self,
_: Option<&GlobalElementId>,
bounds: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
cx: &mut WindowContext,
) -> Self::PrepaintState {
if bounds.top() > cx.viewport_size().height || bounds.bottom() < Pixels::ZERO {
self.view.set_visible(false).unwrap();
} else {
self.view.set_visible(true).unwrap();

self.view
.set_bounds(Rect {
size: dpi::Size::Logical(LogicalSize {
width: (bounds.size.width.0).into(),
height: (bounds.size.height.0).into(),
}),
position: dpi::Position::Logical(dpi::LogicalPosition::new(
bounds.origin.x.into(),
bounds.origin.y.into(),
)),
})
.unwrap();
}
}

fn paint(
&mut self,
_: Option<&GlobalElementId>,
_: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState,
_: &mut WindowContext,
) {
// Nothing to do here
}
}
53 changes: 41 additions & 12 deletions crates/gpui/src/app.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{
any::{type_name, TypeId},
cell::{Ref, RefCell, RefMut},
cell::RefCell,
marker::PhantomData,
ops::{Deref, DerefMut},
path::{Path, PathBuf},
Expand All @@ -10,7 +10,6 @@ use std::{
};

use anyhow::{anyhow, Result};
use derive_more::{Deref, DerefMut};
use futures::{
channel::oneshot,
future::{LocalBoxFuture, Shared},
Expand Down Expand Up @@ -50,7 +49,7 @@ pub const SHUTDOWN_TIMEOUT: Duration = Duration::from_millis(100);
/// Strongly consider removing after stabilization.
#[doc(hidden)]
pub struct AppCell {
app: RefCell<AppContext>,
app: AppContext,
}

impl AppCell {
Expand All @@ -61,7 +60,10 @@ impl AppCell {
let thread_id = std::thread::current().id();
eprintln!("borrowed {thread_id:?}");
}
AppRef(self.app.borrow())
AppRef {
_mark: PhantomData,
app: &self.app as *const _,
}
}

#[doc(hidden)]
Expand All @@ -71,13 +73,26 @@ impl AppCell {
let thread_id = std::thread::current().id();
eprintln!("borrowed {thread_id:?}");
}
AppRefMut(self.app.borrow_mut())
AppRefMut {
_mark: PhantomData,
app: &self.app as *const _ as *mut _,
}
}
}

#[doc(hidden)]
#[derive(Deref, DerefMut)]
pub struct AppRef<'a>(Ref<'a, AppContext>);
pub struct AppRef<'a> {
_mark: PhantomData<&'a ()>,
app: *const AppContext,
}

impl<'a> Deref for AppRef<'a> {
type Target = AppContext;

fn deref(&self) -> &Self::Target {
unsafe { &*self.app }
}
}

impl<'a> Drop for AppRef<'a> {
fn drop(&mut self) {
Expand All @@ -89,9 +104,24 @@ impl<'a> Drop for AppRef<'a> {
}

#[doc(hidden)]
#[derive(Deref, DerefMut)]
pub struct AppRefMut<'a>(RefMut<'a, AppContext>);
pub struct AppRefMut<'a> {
_mark: PhantomData<&'a ()>,
app: *mut AppContext,
}

impl<'a> Deref for AppRefMut<'a> {
type Target = AppContext;

fn deref(&self) -> &Self::Target {
unsafe { &*self.app }
}
}

impl<'a> DerefMut for AppRefMut<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *self.app }
}
}
impl<'a> Drop for AppRefMut<'a> {
fn drop(&mut self) {
if option_env!("TRACK_THREAD_BORROWS").is_some() {
Expand Down Expand Up @@ -284,7 +314,7 @@ impl AppContext {
let keyboard_layout = SharedString::from(platform.keyboard_layout());

let app = Rc::new_cyclic(|this| AppCell {
app: RefCell::new(AppContext {
app: AppContext {
this: this.clone(),
platform: platform.clone(),
text_system,
Expand Down Expand Up @@ -320,10 +350,9 @@ impl AppContext {
layout_id_buffer: Default::default(),
propagate_event: true,
prompt_builder: Some(PromptBuilder::Default),

#[cfg(any(test, feature = "test-support", debug_assertions))]
name: None,
}),
},
});

init_app_menus(platform.as_ref(), &mut app.borrow_mut());
Expand Down
4 changes: 3 additions & 1 deletion crates/gpui/src/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,9 @@ pub(crate) struct RequestFrameOptions {
pub(crate) require_presentation: bool,
}

pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
#[doc(hidden)]
#[allow(private_interfaces)]
pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
fn bounds(&self) -> Bounds<Pixels>;
fn is_maximized(&self) -> bool;
fn window_bounds(&self) -> WindowBounds;
Expand Down
5 changes: 4 additions & 1 deletion crates/gpui/src/platform/linux/wayland/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,10 @@ where

impl rwh::HasWindowHandle for WaylandWindow {
fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
unimplemented!()
let state = self.borrow();
let window = NonNull::new(state.surface.id().as_ptr().cast::<c_void>()).unwrap();
let handle = rwh::WaylandWindowHandle::new(window);
Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
}
}
impl rwh::HasDisplayHandle for WaylandWindow {
Expand Down
3 changes: 2 additions & 1 deletion crates/gpui/src/platform/linux/x11/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,8 @@ impl rwh::HasDisplayHandle for RawWindow {

impl rwh::HasWindowHandle for X11Window {
fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
unimplemented!()
let mut handle = rwh::XlibWindowHandle::new(self.0.x_window as u64);
Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
}
}
impl rwh::HasDisplayHandle for X11Window {
Expand Down
5 changes: 5 additions & 0 deletions crates/gpui/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,11 @@ impl<'a> WindowContext<'a> {
self.window.handle
}

/// Obtain a raw handle to the platform_window that belongs to this context.
pub fn raw_window_handle(&self) -> &dyn PlatformWindow {
self.window.platform_window.as_ref()
}

/// Mark the window as dirty, scheduling it to be redrawn on the next frame.
pub fn refresh(&mut self) {
if self.window.draw_phase == DrawPhase::None {
Expand Down