#[cfg(target_os = "linux")]
fn main() {
demo::run();
}
#[cfg(not(target_os = "linux"))]
fn main() {
eprintln!(
"interop_cube_drag is a Linux example: it renders into a shareable Vulkan image and exports it \
as a dma-buf for the window's renderer. Run it on Linux."
);
}
#[cfg(target_os = "linux")]
#[path = "common/cube.rs"]
mod cube;
#[cfg(target_os = "linux")]
mod demo {
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use gpui::{
AnyElement, App, Bounds, Context, IntoElement, Render, ScrollDelta, Window, WindowBounds,
WindowOptions, div, gpu_canvas, prelude::*, px, rgb, rgba, size,
};
use gpui_interop::{SurfaceEvent, SurfaceInput, SurfacePoint, surface_input};
use crate::cube::{Cube, TRANSPARENT};
const ZOOM_PER_NOTCH: f32 = 0.9;
const PIXELS_PER_LINE: f32 = 40.0;
const TILE: u32 = 640;
const SENSITIVITY: f32 = 0.006;
pub fn run() {
gpui::application().run(|cx: &mut App| {
let bounds = Bounds::centered(None, size(px(900.), px(640.)), cx);
cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
..Default::default()
},
|_, cx| cx.new(|_| Showcase::new()),
)
.expect("a window to composite into");
cx.activate(true);
});
}
struct Showcase {
cube: Rc<RefCell<Option<Cube>>>,
error: Rc<RefCell<Option<String>>>,
last: Rc<Cell<Option<SurfacePoint>>>,
grab: Rc<Cell<bool>>,
spinning: Rc<Cell<bool>>,
picked: Rc<Cell<Option<&'static str>>>,
requested: u32,
}
impl Showcase {
fn new() -> Self {
Self {
cube: Rc::new(RefCell::new(None)),
error: Rc::new(RefCell::new(None)),
last: Rc::new(Cell::new(None)),
grab: Rc::new(Cell::new(false)),
spinning: Rc::new(Cell::new(false)),
picked: Rc::new(Cell::new(None)),
requested: 0,
}
}
}
impl Render for Showcase {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let built = self.cube.borrow().is_some();
let spinning = self.spinning.get();
if spinning || (!built && self.requested < 2) {
if !built {
self.requested += 1;
}
window.request_animation_frame();
}
let status = match (self.cube.borrow().as_ref(), self.error.borrow().as_ref()) {
(Some(cube), _) => format!(
"{} · {} frames rendered, {} dropped",
cube.gpu(),
cube.rendered(),
cube.dropped()
),
(None, Some(error)) => error.clone(),
(None, None) => "negotiating with the window's renderer…".to_owned(),
};
div()
.relative()
.size_full()
.overflow_hidden()
.bg(rgb(0x0b0b0f))
.child(
div()
.size_full()
.flex()
.items_center()
.justify_center()
.child(
div()
.h_full()
.aspect_square()
.child(under(self.picked.clone()))
.child(canvas(
self.cube.clone(),
self.error.clone(),
self.last.clone(),
self.grab.clone(),
self.spinning.clone(),
)),
),
)
.child(panel(status, self.picked.get()))
.child(bar(cx, self.spinning.clone()))
}
}
fn canvas(
cube: Rc<RefCell<Option<Cube>>>,
error: Rc<RefCell<Option<String>>>,
last: Rc<Cell<Option<SurfacePoint>>>,
grab: Rc<Cell<bool>>,
spinning: Rc<Cell<bool>>,
) -> AnyElement {
let drawing = cube.clone();
let zooming = cube.clone();
let covering = cube.clone();
let surface = gpu_canvas(move |gpu| {
let mut cube = drawing.borrow_mut();
if cube.is_none() {
match Cube::build(gpu, TILE, TRANSPARENT) {
Ok(built) => *cube = Some(built),
Err(build) => {
log::error!("interop_cube_drag: cannot build the cube: {build:#}");
*error.borrow_mut() = Some(format!("{build}"));
return;
}
}
}
let cube = cube.as_mut().expect("the cube was just built");
if spinning.get() {
cube.turn(0.013, 0.0078);
}
if let Some(handle) = cube.frame() {
gpu.paint_surface(handle);
}
})
.occlude_where(move |uv| {
covering
.borrow()
.as_ref()
.is_some_and(|cube| cube.covers(uv))
})
.on_scroll_wheel(move |event, window, _cx| {
let notches = match event.delta {
ScrollDelta::Lines(lines) => lines.y,
ScrollDelta::Pixels(pixels) => pixels.y.as_f32() / PIXELS_PER_LINE,
};
let mut cube = zooming.borrow_mut();
let Some(cube) = cube.as_mut() else {
return;
};
cube.zoom(ZOOM_PER_NOTCH.powf(notches));
window.refresh();
})
.absolute()
.size_full();
let handler = move |input: SurfaceInput| {
let mut cube = cube.borrow_mut();
let Some(cube) = cube.as_mut() else {
return;
};
match input.event {
SurfaceEvent::PointerDown { position, .. } => {
grab.set(true);
last.set(Some(position));
}
SurfaceEvent::PointerMove { position, .. } if grab.get() => {
if let Some(previous) = last.replace(Some(position)) {
cube.turn(
(position.x - previous.x) * SENSITIVITY,
(position.y - previous.y) * SENSITIVITY,
);
}
}
SurfaceEvent::PointerMove { .. } => {}
SurfaceEvent::PointerUp { .. } => {
grab.set(false);
last.set(None);
}
}
};
surface_input(surface, handler).into_any_element()
}
fn under(picked: Rc<Cell<Option<&'static str>>>) -> AnyElement {
div()
.absolute()
.size_full()
.flex()
.items_center()
.justify_center()
.gap_6()
.child(tile("Cut", picked.clone()))
.child(tile("Copy", picked.clone()))
.child(tile("Paste", picked.clone()))
.child(tile("Undo", picked.clone()))
.child(tile("Redo", picked))
.into_any_element()
}
fn tile(label: &'static str, picked: Rc<Cell<Option<&'static str>>>) -> AnyElement {
div()
.id(label)
.px_5()
.py_3()
.rounded_md()
.bg(rgba(0xffffff2e))
.border_1()
.border_color(rgba(0xffffff42))
.text_sm()
.text_color(rgb(0xe7eaf0))
.cursor_pointer()
.hover(|style| style.bg(rgba(0xffffff52)).border_color(rgb(0x8fb7ff)))
.on_click(move |_, window, _| {
picked.set(Some(label));
window.refresh();
})
.child(label)
.into_any_element()
}
fn panel(status: String, picked: Option<&'static str>) -> AnyElement {
div()
.absolute()
.top(px(16.))
.left(px(16.))
.flex()
.flex_col()
.gap_1()
.p_3()
.rounded_lg()
.bg(rgba(0x0a0a0fdd))
.border_1()
.border_color(rgba(0xffffff14))
.child(
div()
.text_lg()
.text_color(rgb(0xffffff))
.child("Foreign 3D viewport"),
)
.child(
div()
.text_sm()
.text_color(rgb(0xa8a8b2))
.max_w(px(240.))
.child(
"Drag to turn the cube; scroll to zoom. Where the cube is not, the transparent \
frame hands clicks to the tiles behind.",
),
)
.child(div().text_xs().text_color(rgb(0x6f8fbf)).child(status))
.child(div().text_xs().text_color(rgb(0x8fb7ff)).child(match picked {
Some(label) => format!("clicked through: {label}"),
None => "click a tile in the gap".to_owned(),
}))
.into_any_element()
}
fn bar(cx: &mut Context<Showcase>, spinning: Rc<Cell<bool>>) -> AnyElement {
let reset = div()
.id("cube-reset")
.occlude()
.px_3()
.py_1()
.rounded_md()
.bg(rgba(0xffffff1a))
.text_sm()
.text_color(rgb(0xececf2))
.cursor_pointer()
.hover(|style| style.bg(rgba(0xffffff2e)))
.on_click(cx.listener(|this, _, _, cx| {
if let Some(cube) = this.cube.borrow_mut().as_mut() {
cube.reset();
}
cx.notify();
}))
.child("Reset");
let label = if spinning.get() { "Still" } else { "Spin" };
let spin = div()
.id("cube-spin")
.occlude()
.px_3()
.py_1()
.rounded_md()
.bg(rgba(0xffffff1a))
.text_sm()
.text_color(rgb(0xececf2))
.cursor_pointer()
.hover(|style| style.bg(rgba(0xffffff2e)))
.on_click(cx.listener(move |this, _, _, cx| {
this.spinning.set(!this.spinning.get());
cx.notify();
}))
.child(label);
div()
.absolute()
.bottom(px(16.))
.left_0()
.right_0()
.flex()
.flex_row()
.justify_center()
.gap_2()
.child(reset)
.child(spin)
.into_any_element()
}
}