Files
ELY-Browser/crates/ely_app/src/shell/web_surface_view.rs
T

193 lines
6.3 KiB
Rust

use ely_domain::{BrowserTab, TabId};
use gpui::{
AnyElement, App, Entity, ImageSource, InteractiveElement, IntoElement, MouseButton, ObjectFit,
ParentElement, Styled, StyledImage, Window, canvas, div, img, px, rgb,
};
use super::{ElyShell, web_surface_frame::WebSurfaceFrame};
use ely_design_system::colors;
pub(super) fn render_ready_web_surface(
frame: &WebSurfaceFrame,
tab: &BrowserTab,
state_entity: Entity<ElyShell>,
) -> AnyElement {
// T14: the `gpui::surface(...)` hardware path is disabled.
//
// GPUI 0.2.2's Blade Metal renderer hard-asserts that any
// CVPixelBuffer handed to `surface(...)` is NV12 YUV
// (kCVPixelFormatType_420YpCbCr8BiPlanarFullRange). See
// ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/
// gpui-0.2.2/src/platform/blade/blade_renderer.rs:832
// for the assert. Our sidecar produces BGRA IOSurfaces, so
// calling `surface()` with that buffer panics the renderer on
// the first frame.
//
// We keep `services::iosurface_metal` and
// `WebSurfaceFrame::pixel_buffer` intact so the wire-side
// IOSurfaceHandle import path stays exercised; once GPUI gains a
// BGRA-capable Surface element this branch can come back. Until
// then every frame must go through `img()` below.
if let Some(image) = frame.image.as_ref() {
return render_web_surface(
tab,
state_entity,
img(ImageSource::Render(image.clone())).size_full().object_fit(ObjectFit::Fill),
);
}
// Both image variants empty: the sidecar should always publish
// RGBA while the hardware path is disabled, but a blank canvas is
// the honest user-facing fallback if it ever does not.
render_web_surface(tab, state_entity, div().size_full())
}
pub(super) fn render_loading_web_surface(
tab: &BrowserTab,
state_entity: Entity<ElyShell>,
) -> AnyElement {
render_web_surface(tab, state_entity, div().size_full())
}
pub(super) fn render_failed_web_surface(
tab: &BrowserTab,
message: &str,
state_entity: Entity<ElyShell>,
) -> AnyElement {
render_web_surface(tab, state_entity, error_page(message))
}
fn error_page(message: &str) -> impl IntoElement {
div()
.size_full()
.p(px(48.0))
.flex()
.flex_col()
.items_center()
.justify_center()
.gap(px(12.0))
.child(
div()
.text_size(px(22.0))
.font_weight(gpui::FontWeight(400.0))
.text_color(rgb(colors::INK))
.child("Page unavailable"),
)
.child(
div()
.text_size(px(14.0))
.text_color(rgb(colors::INK_3))
.child(message.to_string()),
)
}
fn render_web_surface(
tab: &BrowserTab,
state_entity: Entity<ElyShell>,
content: impl IntoElement,
) -> AnyElement {
let input_tab_id = tab.id().clone();
let input_url = tab.url().as_str().to_string();
let input_entity = state_entity.clone();
let tracker_entity = state_entity;
div()
.relative()
.size_full()
.min_w_0()
.overflow_hidden()
.child(
div()
.absolute()
.inset_0()
.child(content),
)
.child(render_viewport_tracker(tab.id().clone(), tracker_entity))
.child(render_input_overlay(input_tab_id, input_url, input_entity))
.into_any_element()
}
fn render_input_overlay(
tab_id: TabId,
url: String,
state_entity: Entity<ElyShell>,
) -> impl IntoElement {
let down_entity = state_entity.clone();
let click_tab_id = tab_id.clone();
let click_url = url.clone();
let click_entity = state_entity.clone();
let hover_tab_id = tab_id.clone();
let hover_entity = state_entity.clone();
let scroll_tab_id = tab_id;
let scroll_url = url;
let scroll_entity = state_entity;
div()
.absolute()
.size_full()
.occlude()
// Mouse-down hands focus to the shell's root focus handle so
// subsequent keystrokes route to the web surface instead of
// the omnibar Input. Doing this on mouse-down (not mouse-up)
// lets the user start typing as soon as they press, matching
// native browser focus semantics.
.on_mouse_down(MouseButton::Left, move |_event, window, cx| {
down_entity.update(cx, |shell, _cx| {
shell.focus_web_surface(window);
});
})
.capture_any_mouse_up(move |event, window, cx| {
if event.button != MouseButton::Left {
return;
}
click_entity.update(cx, |shell, cx| {
shell.click_external_web_viewport(
click_tab_id.clone(),
click_url.clone(),
event.position,
window,
cx,
);
});
cx.stop_propagation();
})
.on_mouse_move(move |event, window, cx| {
let scale_factor = window.scale_factor();
hover_entity.update(cx, |shell, cx| {
shell.hover_external_web_viewport(
hover_tab_id.clone(),
event.position,
scale_factor,
cx,
);
});
})
.on_scroll_wheel(move |event, window, cx| {
let delta = event.delta.pixel_delta(window.line_height());
let scale_factor = window.scale_factor();
scroll_entity.update(cx, |shell, cx| {
shell.scroll_external_web_viewport(
scroll_tab_id.clone(),
scroll_url.clone(),
delta,
scale_factor,
cx,
);
});
cx.stop_propagation();
})
}
fn render_viewport_tracker(tab_id: TabId, state_entity: Entity<ElyShell>) -> impl IntoElement {
canvas(
move |bounds, window: &mut Window, cx: &mut App| {
let scale_factor = window.scale_factor();
state_entity.update(cx, |shell, cx| {
shell.record_external_web_viewport(tab_id, bounds, scale_factor, cx);
});
},
|_, _, _, _| {},
)
.absolute()
.size_full()
}