use gpui::{
App, Bounds, Corners, CursorStyle, Element, ElementId, FocusHandle, GlobalElementId, Hitbox,
HitboxBehavior, InspectorElementId, IntoElement, LayoutId, Pixels, RenderImage, Style, Window,
relative,
};
use std::{
cell::Cell,
rc::Rc,
sync::{
Arc,
atomic::{AtomicU64, Ordering},
},
};
use ui::cover::{self, Mark};
pub(crate) trait Surface: 'static {
fn focus(&self) -> Option<&FocusHandle>;
fn owner(&self) -> &Cell<u64>;
fn place(this: &Rc<Self>, bounds: Bounds<Pixels>, window: &mut Window, cx: &mut App);
fn park(&self);
fn cover(&self);
fn still(&self) -> Option<Arc<RenderImage>>;
fn take_dropped(&self) -> Option<Arc<RenderImage>>;
}
pub(crate) struct Host<S: Surface> {
pub(crate) surface: Rc<S>,
}
impl<S: Surface> IntoElement for Host<S> {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}
impl<S: Surface> Element for Host<S> {
type RequestLayoutState = ();
type PrepaintState = (Hitbox, Mark);
fn id(&self) -> Option<ElementId> {
Some("webview".into())
}
fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
None
}
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, ()) {
let mut style = Style::default();
style.size.width = relative(1.).into();
style.size.height = relative(1.).into();
(window.request_layout(style, [], cx), ())
}
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut (),
window: &mut Window,
cx: &mut App,
) -> (Hitbox, Mark) {
if let Some(focus) = self.surface.focus() {
window.set_focus_handle(focus, cx);
}
(
window.insert_hitbox(bounds, HitboxBehavior::BlockMouse),
cover::mark(),
)
}
fn paint(
&mut self,
id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut (),
(hitbox, mark): &mut (Hitbox, Mark),
window: &mut Window,
cx: &mut App,
) {
window.set_cursor_style(CursorStyle::Arrow, hitbox);
let Some(id) = id else { return };
let covered = cover::covered(*mark, bounds, window, cx);
let surface = self.surface.clone();
window.with_element_state::<Shown<S>, _>(id, |shown, window| {
let shown = shown.unwrap_or_else(|| Shown::new(surface.clone()));
surface.owner().set(shown.token);
if covered {
surface.cover();
} else {
S::place(&surface, bounds, window, cx);
}
((), shown)
});
if let Some(still) = self.surface.take_dropped() {
let _ = window.drop_image(still);
}
if let Some(still) = self.surface.still() {
let _ = window.paint_image(bounds, bounds, Corners::default(), still, 0, false);
}
}
}
struct Shown<S: Surface> {
surface: Rc<S>,
token: u64,
}
impl<S: Surface> Shown<S> {
fn new(surface: Rc<S>) -> Self {
static TOKENS: AtomicU64 = AtomicU64::new(1);
Self {
surface,
token: TOKENS.fetch_add(1, Ordering::Relaxed),
}
}
}
impl<S: Surface> Drop for Shown<S> {
fn drop(&mut self) {
if self.surface.owner().get() == self.token {
self.surface.park();
}
}
}