gpui-component/crates/ui/src/webview.rs
Jason Lee 327d40e0e0
chore: Upgrade GPUI 3 (#587)
The pervious version has keep on
[gpui2](https://github.com/longbridge/gpui-component/tree/gpui2) branch,
if there need to use.

Ref https://github.com/zed-industries/zed/pull/22632

## Prepare

### Generate index.scip

```bash
rust-analyzer scip ./ > index.scip
```

### Get [refactor](https://github.com/zed-industries/refactor)

```bash
https://github.com/zed-industries/refactor.git
```

## Refactor exist code

```bash
cd refactor
cargo run -- ~/work/gpui-component
```

Then will get result if successed:

```
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s
     Running `target/debug/refactor /Users/jason/work/gpui-component`
Loaded 115 SCIP documents
Processing files starting with focus path: ""
Changed 90 files
```

-------------

## Changes

```diff
- View<T>
- Model<T>
+ Entity<T>

- WeakView<T>
+ WeakEntity<T>

- FocusableView
+ Focusable

- cx.bounds()
+ window.bounds()
- cx.refresh()
+ window.refresh()
- cx.focused()
+ window.focused(cx)
- cx.with_optional_element_state
- window.with_optional_element_state
```

```diff
- &mut WindowContext
+ &mut Window, &mut App

- cx: &mut WindowContext
+ window: &mut Window, cx: &mut App

- &mut ViewContext<
+ &mut Window, &mut Context<

- cx: &mut ViewContext<
+ window: &mut Window, cx: &mut Context<
```

```diff
- cx.spawn(|_, mut cx| async move { 
+ cx.spawn_in(window, |_, mut cx| async move {
- cx.dispatch_action(Box::new(...))
+ window.dispatch_action(Box::new(...), cx)
```

`cx.spawn`

```diff
- cx.spawn(|view, mut cx| async move {
+ cx.spawn_in(window, |view, mut window| async move {
+     _ = view.update_in(&mut window, move |view, window, cx| {
```
2025-01-27 03:58:48 +08:00

205 lines
5.2 KiB
Rust

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,
};
pub struct WebView {
focus_handle: FocusHandle,
webview: Rc<wry::WebView>,
visible: bool,
bounds: Bounds<Pixels>,
}
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);
}
pub fn hide(&mut self) {
_ = self.webview.focus_parent();
_ = self.webview.set_visible(false);
}
pub fn visible(&self) -> bool {
self.visible
}
pub fn bounds(&self) -> Bounds<Pixels> {
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<DismissEvent> for WebView {}
impl Render for WebView {
fn render(
&mut self,
window: &mut gpui::Window,
cx: &mut gpui::Context<Self>,
) -> impl IntoElement {
let view = cx.model().clone();
div()
.track_focus(&self.focus_handle)
.size_full()
.child({
let view = cx.model().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<WebView>,
view: Rc<wry::WebView>,
}
impl WebViewElement {
/// Create a new webview element from a wry WebView.
pub fn new(
view: Rc<wry::WebView>,
parent: Entity<WebView>,
_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<Hitbox>;
fn id(&self) -> Option<ElementId> {
None
}
fn request_layout(
&mut self,
_: Option<&GlobalElementId>,
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>,
bounds: Bounds<Pixels>,
_: &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.0).into(),
height: (bounds.size.height.0).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, false))
}
fn paint(
&mut self,
_: Option<&GlobalElementId>,
bounds: Bounds<Pixels>,
_: &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();
}
});
});
}
}